Bitcoin¶

Import thư viện¶

In [1]:
import numpy as np
import pandas as pd
import yfinance as yf
import datetime as dt
import matplotlib.pyplot as plt
import math
from keras.models import Sequential
from keras.layers import SimpleRNN, Dense, Dropout
from keras.optimizers import Adam
from keras.callbacks import EarlyStopping, ReduceLROnPlateau
from keras import regularizers
from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, mean_absolute_percentage_error
from typing import Tuple

Bitcoin Dataset¶

Import csv¶

In [2]:
# Đọc file Bitcoin
file_path = "D:\\github_desktop\\Cryptocurrency-Price-Prediction\\Cryptocurrency\\Dataset\\Bitcoin Historical Data.csv"

data = pd.read_csv(file_path)

# Loại bỏ dấu phẩy và chuyển đổi thành float
for col in ['Price', 'Open']:
    data[col] = data[col].str.replace(',', '', regex=False).astype(float)

# Xử lý cột 'Vol.' chứa hậu tố 'K', 'M', 'B'
def convert_volume(val):
    val = str(val).replace(',', '').strip()
    if 'K' in val:
        return float(val.replace('K', '')) * 1_000
    elif 'M' in val:
        return float(val.replace('M', '')) * 1_000_000
    elif 'B' in val:
        return float(val.replace('B', '')) * 1_000_000_000
    else:
        return float(val)

data['Vol.'] = data['Vol.'].apply(convert_volume)

# Đổi Date sang datetime và đặt làm index
data['Date'] = pd.to_datetime(data['Date'])
data.set_index('Date', inplace=True)
data.sort_index(inplace=True)

# Select 3 columns: Price, Open, Vol
data_features = data[['Price', 'Open', 'Vol.']].copy()

print("Data shape:", data.shape)
print("Columns:", data.columns.tolist())
print("\nFirst 5 rows:")
print(data[['Price', 'Open', 'Vol.']].head())
print(f"Tổng số dữ liệu: {len(data)} dòng")
Data shape: (3370, 6)
Columns: ['Price', 'Open', 'High', 'Low', 'Vol.', 'Change %']

First 5 rows:
            Price   Open     Vol.
Date                             
2016-03-10  415.8  412.8  55740.0
2016-03-11  419.1  415.8  60630.0
2016-03-12  410.4  419.1  59640.0
2016-03-13  412.4  410.4  34980.0
2016-03-14  414.3  412.4  49330.0
Tổng số dữ liệu: 3370 dòng

Chia 7:3¶

Chuẩn hóa dữ liệu¶

In [3]:
# Chuẩn hóa dữ liệu
# Lấy 3 cột Price, Open, Vol. để làm đầu vào và Price làm đầu ra
input_features = data[['Price', 'Open', 'Vol.']].values
target_feature = data[['Price']].values

# Áp dụng MinMaxScaler cho input features
scaler_input = MinMaxScaler(feature_range=(0, 1))
scaled_input = scaler_input.fit_transform(input_features)

# Áp dụng MinMaxScaler cho target feature
scaler_target = MinMaxScaler(feature_range=(0, 1))
scaled_target = scaler_target.fit_transform(target_feature)
In [4]:
# Chia dữ liệu train/test theo tỷ lệ 7:3
train_size = int(len(data) * 0.7)
train_data = data.iloc[0:train_size,:]
test_data = data.iloc[train_size:len(data),:]

# Chia dữ liệu đã chuẩn hóa
scaled_train_input = scaled_input[0:train_size,:]
scaled_test_input = scaled_input[train_size:,:]
scaled_train_target = scaled_target[0:train_size,:]
scaled_test_target = scaled_target[train_size:,:]

print(f"Kích thước tập train: {len(train_data)}")
print(f"Kích thước tập test: {len(test_data)}")
Kích thước tập train: 2359
Kích thước tập test: 1011

Xây dựng mô hình RNN¶

In [5]:
    """
    Xây dựng mô hình RNN với regularization

    Args:
        time_step: Số time steps để nhìn về quá khứ
        num_features: Số features đầu vào (Price, Open, Vol = 3)

    Returns:
        Sequential model
    """
Out[5]:
'\nXây dựng mô hình RNN với regularization\n\nArgs:\n    time_step: Số time steps để nhìn về quá khứ\n    num_features: Số features đầu vào (Price, Open, Vol = 3)\n\nReturns:\n    Sequential model\n'
In [6]:
def build_rnn_model_with_regularization(time_step: int, num_features: int) -> Sequential:
    model = Sequential()

    # SimpleRNN layer với regularization
    model.add(SimpleRNN(
        units=50,                       # Số neurons
        input_shape=(time_step, num_features),  # (50, 3)
        kernel_regularizer=regularizers.l2(0.001),    # L2 regularization
        return_sequences=False           # Chỉ cần output cuối cùng
    ))

    # Dropout để tránh overfitting
    model.add(Dropout(0.3))

    # Dense layer ẩn
    model.add(Dense(32, activation='relu'))
    model.add(Dropout(0.2))

    # Output layer: dự đoán 1 giá trị (Price)
    model.add(Dense(1))

    # Optimizer với learning rate nhỏ
    optimizer = Adam(learning_rate=1e-4)
    model.compile(optimizer=optimizer, loss='mean_squared_error', metrics=['mae'])

    return model
In [7]:
# Hàm tạo dữ liệu time series với multiple features
def create_multivariate_time_series_data(input_data: np.ndarray, target_data: np.ndarray, time_step: int) -> Tuple[np.ndarray, np.ndarray]:
    X_data, y_data = [], []
    for i in range(len(input_data) - time_step):
        X_data.append(input_data[i:(i + time_step), :])  # Lấy tất cả features
        y_data.append(target_data[i + time_step, 0])     # Chỉ lấy Price
    return np.array(X_data), np.array(y_data)
In [8]:
# Hàm dự đoán với multiple features
def forecast_multivariate_prices(model: Sequential, input_data: np.ndarray, time_step: int,
                                forecast_days: int, scaler_target: MinMaxScaler) -> np.ndarray:
    temp_input = input_data[-time_step:].reshape(1, time_step, input_data.shape[1])
    lst_output = []

    for _ in range(forecast_days):
        predicted_price = model.predict(temp_input, verbose=0)
        lst_output.append(predicted_price[0].tolist())

        # Tạo input mới cho prediction tiếp theo
        # Giả sử các features khác không đổi, chỉ cập nhật Price
        new_row = temp_input[0, -1, :].copy()
        new_row[0] = predicted_price[0, 0]  # Cập nhật Price prediction

        temp_input = np.append(temp_input[:, 1:, :],
                              new_row.reshape(1, 1, input_data.shape[1]), axis=1)

    # Chuyển đổi lst_output thành numpy array và inverse transform
    lst_output = np.array(lst_output).reshape(-1, 1)
    return scaler_target.inverse_transform(lst_output)

Huấn luyện mô hình¶

In [9]:
# Tạo dữ liệu train và test với time_step = 50
time_step = 50
X_train, y_train = create_multivariate_time_series_data(scaled_train_input, scaled_train_target, time_step)
X_test, y_test = create_multivariate_time_series_data(scaled_test_input, scaled_test_target, time_step)

print(f"X_train shape: {X_train.shape}")
print(f"y_train shape: {y_train.shape}")
print(f"X_test shape: {X_test.shape}")
print(f"y_test shape: {y_test.shape}")

# Xây dựng mô hình RNN
model_rnn = build_rnn_model_with_regularization(time_step, 3)  # 3 features: Price, Open, Vol

# Callbacks để tối ưu hóa
early_stop = EarlyStopping(monitor='val_loss', patience=15, restore_best_weights=True, verbose=1)
reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=10, min_lr=1e-7, verbose=1)

# Huấn luyện mô hình
history_rnn = model_rnn.fit(
    X_train, y_train,
    epochs=60,
    batch_size=16,
    validation_data=(X_test, y_test),
    callbacks=[early_stop, reduce_lr],
    verbose=1
)
X_train shape: (2309, 50, 3)
y_train shape: (2309,)
X_test shape: (961, 50, 3)
y_test shape: (961,)
Epoch 1/60
c:\Users\Hii\AppData\Local\Programs\Python\Python311\Lib\site-packages\keras\src\layers\rnn\rnn.py:199: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead.
  super().__init__(**kwargs)
145/145 ━━━━━━━━━━━━━━━━━━━━ 3s 8ms/step - loss: 0.0712 - mae: 0.1481 - val_loss: 0.0605 - val_mae: 0.1607 - learning_rate: 1.0000e-04
Epoch 2/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0157 - mae: 0.0581 - val_loss: 0.0232 - val_mae: 0.0811 - learning_rate: 1.0000e-04
Epoch 3/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0122 - mae: 0.0465 - val_loss: 0.0198 - val_mae: 0.0839 - learning_rate: 1.0000e-04
Epoch 4/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0110 - mae: 0.0440 - val_loss: 0.0151 - val_mae: 0.0677 - learning_rate: 1.0000e-04
Epoch 5/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0092 - mae: 0.0384 - val_loss: 0.0157 - val_mae: 0.0713 - learning_rate: 1.0000e-04
Epoch 6/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0092 - mae: 0.0383 - val_loss: 0.0116 - val_mae: 0.0515 - learning_rate: 1.0000e-04
Epoch 7/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0087 - mae: 0.0344 - val_loss: 0.0087 - val_mae: 0.0388 - learning_rate: 1.0000e-04
Epoch 8/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0085 - mae: 0.0353 - val_loss: 0.0082 - val_mae: 0.0366 - learning_rate: 1.0000e-04
Epoch 9/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0084 - mae: 0.0341 - val_loss: 0.0081 - val_mae: 0.0375 - learning_rate: 1.0000e-04
Epoch 10/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0079 - mae: 0.0321 - val_loss: 0.0063 - val_mae: 0.0266 - learning_rate: 1.0000e-04
Epoch 11/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0075 - mae: 0.0306 - val_loss: 0.0062 - val_mae: 0.0277 - learning_rate: 1.0000e-04
Epoch 12/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0077 - mae: 0.0305 - val_loss: 0.0083 - val_mae: 0.0486 - learning_rate: 1.0000e-04
Epoch 13/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0067 - mae: 0.0266 - val_loss: 0.0062 - val_mae: 0.0274 - learning_rate: 1.0000e-04
Epoch 14/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0067 - mae: 0.0276 - val_loss: 0.0055 - val_mae: 0.0250 - learning_rate: 1.0000e-04
Epoch 15/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0065 - mae: 0.0265 - val_loss: 0.0058 - val_mae: 0.0282 - learning_rate: 1.0000e-04
Epoch 16/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0067 - mae: 0.0283 - val_loss: 0.0056 - val_mae: 0.0263 - learning_rate: 1.0000e-04
Epoch 17/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0065 - mae: 0.0270 - val_loss: 0.0059 - val_mae: 0.0327 - learning_rate: 1.0000e-04
Epoch 18/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0062 - mae: 0.0259 - val_loss: 0.0053 - val_mae: 0.0244 - learning_rate: 1.0000e-04
Epoch 19/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0063 - mae: 0.0269 - val_loss: 0.0052 - val_mae: 0.0233 - learning_rate: 1.0000e-04
Epoch 20/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0061 - mae: 0.0263 - val_loss: 0.0048 - val_mae: 0.0220 - learning_rate: 1.0000e-04
Epoch 21/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0060 - mae: 0.0258 - val_loss: 0.0046 - val_mae: 0.0192 - learning_rate: 1.0000e-04
Epoch 22/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 9ms/step - loss: 0.0058 - mae: 0.0260 - val_loss: 0.0043 - val_mae: 0.0161 - learning_rate: 1.0000e-04
Epoch 23/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0055 - mae: 0.0250 - val_loss: 0.0051 - val_mae: 0.0307 - learning_rate: 1.0000e-04
Epoch 24/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0053 - mae: 0.0240 - val_loss: 0.0046 - val_mae: 0.0251 - learning_rate: 1.0000e-04
Epoch 25/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0054 - mae: 0.0253 - val_loss: 0.0045 - val_mae: 0.0227 - learning_rate: 1.0000e-04
Epoch 26/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0052 - mae: 0.0251 - val_loss: 0.0043 - val_mae: 0.0204 - learning_rate: 1.0000e-04
Epoch 27/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0048 - mae: 0.0223 - val_loss: 0.0045 - val_mae: 0.0247 - learning_rate: 1.0000e-04
Epoch 28/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0049 - mae: 0.0233 - val_loss: 0.0053 - val_mae: 0.0351 - learning_rate: 1.0000e-04
Epoch 29/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0048 - mae: 0.0233 - val_loss: 0.0052 - val_mae: 0.0357 - learning_rate: 1.0000e-04
Epoch 30/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0046 - mae: 0.0227 - val_loss: 0.0041 - val_mae: 0.0225 - learning_rate: 1.0000e-04
Epoch 31/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 9ms/step - loss: 0.0047 - mae: 0.0233 - val_loss: 0.0038 - val_mae: 0.0188 - learning_rate: 1.0000e-04
Epoch 32/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0044 - mae: 0.0228 - val_loss: 0.0035 - val_mae: 0.0144 - learning_rate: 1.0000e-04
Epoch 33/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0044 - mae: 0.0233 - val_loss: 0.0035 - val_mae: 0.0154 - learning_rate: 1.0000e-04
Epoch 34/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 9ms/step - loss: 0.0042 - mae: 0.0214 - val_loss: 0.0033 - val_mae: 0.0142 - learning_rate: 1.0000e-04
Epoch 35/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0043 - mae: 0.0235 - val_loss: 0.0034 - val_mae: 0.0163 - learning_rate: 1.0000e-04
Epoch 36/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0041 - mae: 0.0220 - val_loss: 0.0032 - val_mae: 0.0150 - learning_rate: 1.0000e-04
Epoch 37/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0040 - mae: 0.0221 - val_loss: 0.0031 - val_mae: 0.0133 - learning_rate: 1.0000e-04
Epoch 38/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0040 - mae: 0.0226 - val_loss: 0.0031 - val_mae: 0.0139 - learning_rate: 1.0000e-04
Epoch 39/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0037 - mae: 0.0213 - val_loss: 0.0032 - val_mae: 0.0167 - learning_rate: 1.0000e-04
Epoch 40/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0038 - mae: 0.0216 - val_loss: 0.0032 - val_mae: 0.0173 - learning_rate: 1.0000e-04
Epoch 41/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 9ms/step - loss: 0.0038 - mae: 0.0219 - val_loss: 0.0031 - val_mae: 0.0174 - learning_rate: 1.0000e-04
Epoch 42/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0036 - mae: 0.0207 - val_loss: 0.0039 - val_mae: 0.0301 - learning_rate: 1.0000e-04
Epoch 43/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0037 - mae: 0.0223 - val_loss: 0.0030 - val_mae: 0.0181 - learning_rate: 1.0000e-04
Epoch 44/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 9ms/step - loss: 0.0034 - mae: 0.0213 - val_loss: 0.0028 - val_mae: 0.0157 - learning_rate: 1.0000e-04
Epoch 45/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0032 - mae: 0.0204 - val_loss: 0.0027 - val_mae: 0.0150 - learning_rate: 1.0000e-04
Epoch 46/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 9ms/step - loss: 0.0032 - mae: 0.0204 - val_loss: 0.0025 - val_mae: 0.0127 - learning_rate: 1.0000e-04
Epoch 47/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0033 - mae: 0.0215 - val_loss: 0.0034 - val_mae: 0.0271 - learning_rate: 1.0000e-04
Epoch 48/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0032 - mae: 0.0208 - val_loss: 0.0024 - val_mae: 0.0137 - learning_rate: 1.0000e-04
Epoch 49/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0032 - mae: 0.0213 - val_loss: 0.0027 - val_mae: 0.0191 - learning_rate: 1.0000e-04
Epoch 50/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0030 - mae: 0.0207 - val_loss: 0.0030 - val_mae: 0.0247 - learning_rate: 1.0000e-04
Epoch 51/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0027 - mae: 0.0190 - val_loss: 0.0022 - val_mae: 0.0138 - learning_rate: 1.0000e-04
Epoch 52/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0028 - mae: 0.0197 - val_loss: 0.0022 - val_mae: 0.0143 - learning_rate: 1.0000e-04
Epoch 53/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0027 - mae: 0.0194 - val_loss: 0.0028 - val_mae: 0.0229 - learning_rate: 1.0000e-04
Epoch 54/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0027 - mae: 0.0193 - val_loss: 0.0034 - val_mae: 0.0312 - learning_rate: 1.0000e-04
Epoch 55/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0025 - mae: 0.0189 - val_loss: 0.0028 - val_mae: 0.0247 - learning_rate: 1.0000e-04
Epoch 56/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0026 - mae: 0.0195 - val_loss: 0.0024 - val_mae: 0.0199 - learning_rate: 1.0000e-04
Epoch 57/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0024 - mae: 0.0186 - val_loss: 0.0022 - val_mae: 0.0175 - learning_rate: 1.0000e-04
Epoch 58/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0026 - mae: 0.0201 - val_loss: 0.0026 - val_mae: 0.0226 - learning_rate: 1.0000e-04
Epoch 59/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0023 - mae: 0.0187 - val_loss: 0.0024 - val_mae: 0.0219 - learning_rate: 1.0000e-04
Epoch 60/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0023 - mae: 0.0187 - val_loss: 0.0018 - val_mae: 0.0143 - learning_rate: 1.0000e-04
Restoring model weights from the end of the best epoch: 60.

Đánh giá mô hình¶

In [10]:
# Vẽ val_loss để đánh giá overfitting
plt.figure(figsize=(12, 8))

plt.subplot(2, 1, 1)
plt.plot(history_rnn.history['loss'], label='Training Loss', linewidth=2)
plt.plot(history_rnn.history['val_loss'], label='Validation Loss', linewidth=2)
plt.title('RNN Model Loss - Training vs Validation')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# Tìm epoch có val_loss thấp nhất
best_epoch = np.argmin(history_rnn.history['val_loss']) + 1
best_val_loss = min(history_rnn.history['val_loss'])
print(f"Epoch tốt nhất: {best_epoch} với val_loss: {best_val_loss:.6f}")
No description has been provided for this image
Epoch tốt nhất: 60 với val_loss: 0.001849

Dự đoán và trực quan hóa¶

In [11]:
# Dự đoán 30, 60, 90 ngày tiếp theo
last_data_scaled = scaled_test_input[-time_step:]

forecasted_prices_30 = forecast_multivariate_prices(model_rnn, last_data_scaled, time_step, 30, scaler_target)
forecasted_prices_60 = forecast_multivariate_prices(model_rnn, last_data_scaled, time_step, 60, scaler_target)
forecasted_prices_90 = forecast_multivariate_prices(model_rnn, last_data_scaled, time_step, 90, scaler_target)

# Dự đoán giá trên tập kiểm tra
test_predict_scaled = model_rnn.predict(X_test)
test_predict_rnn = scaler_target.inverse_transform(test_predict_scaled)

# Tạo DataFrame cho các dự đoán
forecast_dates_30 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=30, freq='D')
forecast_dates_60 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=60, freq='D')
forecast_dates_90 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=90, freq='D')

forecast_df_30 = pd.DataFrame(forecasted_prices_30, index=forecast_dates_30, columns=['Price'])
forecast_df_60 = pd.DataFrame(forecasted_prices_60, index=forecast_dates_60, columns=['Price'])
forecast_df_90 = pd.DataFrame(forecasted_prices_90, index=forecast_dates_90, columns=['Price'])

# Trực quan hóa kết quả
plt.figure(figsize=(16, 10))

# Vẽ giá thực tế
plt.plot(data.index, data['Price'], label='Giá thực tế', color='blue', linewidth=2, alpha=0.8)

# Vẽ dự đoán trên tập test
plt.plot(test_data.index[time_step:], test_predict_rnn,
         label='Dự đoán trên tập test', color='orange', linewidth=2, alpha=0.8)

# Vẽ các dự đoán tương lai
plt.plot(forecast_df_30.index, forecast_df_30['Price'],
         label='Dự đoán 30 ngày tiếp theo', color='red', linestyle='--', linewidth=2, alpha=0.7)

plt.plot(forecast_df_60.index, forecast_df_60['Price'],
         label='Dự đoán 60 ngày tiếp theo', color='green', linestyle='--', linewidth=2, alpha=0.5)

plt.plot(forecast_df_90.index, forecast_df_90['Price'],
         label='Dự đoán 90 ngày tiếp theo', color='purple', linestyle='--', linewidth=2, alpha=0.3)

# Thêm đường thẳng đứng để phân biệt vùng dữ liệu
plt.axvline(x=data.index[-1], color='black', linestyle=':', alpha=0.7,
            label='Ngày cuối cùng của dữ liệu thực tế')

plt.title(f'Dự đoán giá Bitcoin bằng RNN (Time Step = {time_step})\nDự đoán 30, 60, 90 ngày tiếp theo',
          fontsize=16, fontweight='bold')
plt.xlabel('Ngày', fontsize=12)
plt.ylabel('Giá (USD)', fontsize=12)
plt.legend(fontsize=10, loc='upper left')
plt.grid(True, alpha=0.3)

plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
31/31 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step
No description has been provided for this image
In [12]:
# Đánh giá mô hình
# Lấy giá trị thực tế trên tập test
y_test_actual = test_data['Price'].values[time_step:]

# Tính toán các metrics
mape = mean_absolute_percentage_error(y_test_actual, test_predict_rnn.flatten())
mse = mean_squared_error(y_test_actual, test_predict_rnn.flatten())
rmse = np.sqrt(mse)

print(f'Kết quả đánh giá mô hình RNN (Time Step = {time_step}):')
print(f'MAPE: {mape:.2f}%')
print(f'MSE: {mse:.2f}')
print(f'RMSE: {rmse:.2f}')
print(f'Số epochs huấn luyện: {len(history_rnn.history["loss"])}')

# Hiển thị thông tin dự đoán 30 ngày
print(f'\nDự đoán giá Bitcoin 30 ngày tiếp theo:')
print(f'Giá cao nhất: ${forecasted_prices_30.max():.2f}')
print(f'Giá thấp nhất: ${forecasted_prices_30.min():.2f}')
print(f'Giá trung bình: ${forecasted_prices_30.mean():.2f}')
Kết quả đánh giá mô hình RNN (Time Step = 50):
MAPE: 0.03%
MSE: 5527311.55
RMSE: 2351.02
Số epochs huấn luyện: 60

Dự đoán giá Bitcoin 30 ngày tiếp theo:
Giá cao nhất: $101692.81
Giá thấp nhất: $87836.89
Giá trung bình: $92697.28

Chia 8:2¶

Chuẩn hóa dữ liệu 8:2¶

In [13]:
# Chuẩn hóa dữ liệu
# Lấy 3 cột Price, Open, Vol. để làm đầu vào và Price làm đầu ra
input_features = data[['Price', 'Open', 'Vol.']].values
target_feature = data[['Price']].values

# Áp dụng MinMaxScaler cho input features
scaler_input = MinMaxScaler(feature_range=(0, 1))
scaled_input = scaler_input.fit_transform(input_features)

# Áp dụng MinMaxScaler cho target feature
scaler_target = MinMaxScaler(feature_range=(0, 1))
scaled_target = scaler_target.fit_transform(target_feature)
In [14]:
# Chia dữ liệu train/test theo tỷ lệ 8:2
train_size_82 = int(len(data) * 0.8)
train_data_82 = data.iloc[0:train_size_82,:]
test_data_82 = data.iloc[train_size_82:len(data),:]

# Chia dữ liệu đã chuẩn hóa
scaled_train_input_82 = scaled_input[0:train_size_82,:]
scaled_test_input_82 = scaled_input[train_size_82:,:]
scaled_train_target_82 = scaled_target[0:train_size_82,:]
scaled_test_target_82 = scaled_target[train_size_82:,:]

print(f"Kích thước tập train 8:2: {len(train_data_82)}")
print(f"Kích thước tập test 8:2: {len(test_data_82)}")
Kích thước tập train 8:2: 2696
Kích thước tập test 8:2: 674

Xây dựng mô hình RNN¶

In [15]:
def build_rnn_model_with_regularization(time_step: int, num_features: int) -> Sequential:
    """
    Xây dựng mô hình RNN với regularization

    Args:
        time_step: Số time steps để nhìn về quá khứ
        num_features: Số features đầu vào (Price, Open, Vol = 3)

    Returns:
        Sequential model
    """
    model = Sequential()

    # SimpleRNN layer với regularization
    model.add(SimpleRNN(
        units=50,                       # Số neurons
        input_shape=(time_step, num_features),  # (50, 3)
        kernel_regularizer=regularizers.l2(0.001),    # L2 regularization
        return_sequences=False           # Chỉ cần output cuối cùng
    ))

    # Dropout để tránh overfitting
    model.add(Dropout(0.3))

    # Dense layer ẩn
    model.add(Dense(32, activation='relu'))
    model.add(Dropout(0.2))

    # Output layer: dự đoán 1 giá trị (Price)
    model.add(Dense(1))

    # Optimizer với learning rate nhỏ
    optimizer = Adam(learning_rate=1e-4)
    model.compile(optimizer=optimizer, loss='mean_squared_error', metrics=['mae'])

    return model
In [16]:
# Hàm tạo dữ liệu time series với multiple features
def create_multivariate_time_series_data(input_data: np.ndarray, target_data: np.ndarray, time_step: int) -> Tuple[np.ndarray, np.ndarray]:
    X_data, y_data = [], []
    for i in range(len(input_data) - time_step):
        X_data.append(input_data[i:(i + time_step), :])  # Lấy tất cả features
        y_data.append(target_data[i + time_step, 0])     # Chỉ lấy Price
    return np.array(X_data), np.array(y_data)
In [17]:
# Hàm dự đoán với multiple features
def forecast_multivariate_prices(model: Sequential, input_data: np.ndarray, time_step: int,
                                forecast_days: int, scaler_target: MinMaxScaler) -> np.ndarray:
    temp_input = input_data[-time_step:].reshape(1, time_step, input_data.shape[1])
    lst_output = []

    for _ in range(forecast_days):
        predicted_price = model.predict(temp_input, verbose=0)
        lst_output.append(predicted_price[0].tolist())

        # Tạo input mới cho prediction tiếp theo
        # Giả sử các features khác không đổi, chỉ cập nhật Price
        new_row = temp_input[0, -1, :].copy()
        new_row[0] = predicted_price[0, 0]  # Cập nhật Price prediction

        temp_input = np.append(temp_input[:, 1:, :],
                              new_row.reshape(1, 1, input_data.shape[1]), axis=1)

    # Chuyển đổi lst_output thành numpy array và inverse transform
    lst_output = np.array(lst_output).reshape(-1, 1)
    return scaler_target.inverse_transform(lst_output)

Huấn luyện mô hình 8:2¶

In [18]:
# Tạo dữ liệu train và test với time_step = 50 cho split 8:2
X_train_82, y_train_82 = create_multivariate_time_series_data(scaled_train_input_82, scaled_train_target_82, time_step)
X_test_82, y_test_82 = create_multivariate_time_series_data(scaled_test_input_82, scaled_test_target_82, time_step)

print(f"X_train_82 shape: {X_train_82.shape}")
print(f"y_train_82 shape: {y_train_82.shape}")
print(f"X_test_82 shape: {X_test_82.shape}")
print(f"y_test_82 shape: {y_test_82.shape}")

# Xây dựng mô hình RNN cho split 8:2
model_rnn_82 = build_rnn_model_with_regularization(time_step, 3)  # 3 features: Price, Open, Vol

# Callbacks để tối ưu hóa
early_stop_82 = EarlyStopping(monitor='val_loss', patience=15, restore_best_weights=True, verbose=1)
reduce_lr_82 = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=10, min_lr=1e-7, verbose=1)

# Huấn luyện mô hình 8:2
history_rnn_82 = model_rnn_82.fit(
    X_train_82, y_train_82,
    epochs=60,
    batch_size=16,
    validation_data=(X_test_82, y_test_82),
    callbacks=[early_stop_82, reduce_lr_82],
    verbose=1
)
X_train_82 shape: (2646, 50, 3)
y_train_82 shape: (2646,)
X_test_82 shape: (624, 50, 3)
y_test_82 shape: (624,)
Epoch 1/60
c:\Users\Hii\AppData\Local\Programs\Python\Python311\Lib\site-packages\keras\src\layers\rnn\rnn.py:199: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead.
  super().__init__(**kwargs)
166/166 ━━━━━━━━━━━━━━━━━━━━ 3s 9ms/step - loss: 0.0558 - mae: 0.1317 - val_loss: 0.1150 - val_mae: 0.2632 - learning_rate: 1.0000e-04
Epoch 2/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0131 - mae: 0.0506 - val_loss: 0.0607 - val_mae: 0.1839 - learning_rate: 1.0000e-04
Epoch 3/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0111 - mae: 0.0423 - val_loss: 0.0459 - val_mae: 0.1529 - learning_rate: 1.0000e-04
Epoch 4/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0107 - mae: 0.0403 - val_loss: 0.0398 - val_mae: 0.1531 - learning_rate: 1.0000e-04
Epoch 5/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0107 - mae: 0.0409 - val_loss: 0.0321 - val_mae: 0.1316 - learning_rate: 1.0000e-04
Epoch 6/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0092 - mae: 0.0346 - val_loss: 0.0253 - val_mae: 0.1144 - learning_rate: 1.0000e-04
Epoch 7/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0089 - mae: 0.0338 - val_loss: 0.0218 - val_mae: 0.1075 - learning_rate: 1.0000e-04
Epoch 8/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0092 - mae: 0.0328 - val_loss: 0.0228 - val_mae: 0.1007 - learning_rate: 1.0000e-04
Epoch 9/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0083 - mae: 0.0300 - val_loss: 0.0216 - val_mae: 0.1018 - learning_rate: 1.0000e-04
Epoch 10/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0083 - mae: 0.0314 - val_loss: 0.0155 - val_mae: 0.0806 - learning_rate: 1.0000e-04
Epoch 11/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0081 - mae: 0.0300 - val_loss: 0.0138 - val_mae: 0.0698 - learning_rate: 1.0000e-04
Epoch 12/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0073 - mae: 0.0273 - val_loss: 0.0137 - val_mae: 0.0711 - learning_rate: 1.0000e-04
Epoch 13/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0075 - mae: 0.0284 - val_loss: 0.0135 - val_mae: 0.0774 - learning_rate: 1.0000e-04
Epoch 14/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0074 - mae: 0.0282 - val_loss: 0.0118 - val_mae: 0.0669 - learning_rate: 1.0000e-04
Epoch 15/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0070 - mae: 0.0268 - val_loss: 0.0096 - val_mae: 0.0511 - learning_rate: 1.0000e-04
Epoch 16/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0065 - mae: 0.0244 - val_loss: 0.0127 - val_mae: 0.0764 - learning_rate: 1.0000e-04
Epoch 17/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0067 - mae: 0.0255 - val_loss: 0.0101 - val_mae: 0.0603 - learning_rate: 1.0000e-04
Epoch 18/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0064 - mae: 0.0248 - val_loss: 0.0113 - val_mae: 0.0704 - learning_rate: 1.0000e-04
Epoch 19/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0061 - mae: 0.0246 - val_loss: 0.0083 - val_mae: 0.0508 - learning_rate: 1.0000e-04
Epoch 20/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0061 - mae: 0.0239 - val_loss: 0.0105 - val_mae: 0.0684 - learning_rate: 1.0000e-04
Epoch 21/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0057 - mae: 0.0240 - val_loss: 0.0074 - val_mae: 0.0450 - learning_rate: 1.0000e-04
Epoch 22/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0055 - mae: 0.0229 - val_loss: 0.0061 - val_mae: 0.0334 - learning_rate: 1.0000e-04
Epoch 23/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0056 - mae: 0.0237 - val_loss: 0.0073 - val_mae: 0.0478 - learning_rate: 1.0000e-04
Epoch 24/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0056 - mae: 0.0229 - val_loss: 0.0090 - val_mae: 0.0621 - learning_rate: 1.0000e-04
Epoch 25/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0052 - mae: 0.0226 - val_loss: 0.0074 - val_mae: 0.0504 - learning_rate: 1.0000e-04
Epoch 26/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0051 - mae: 0.0227 - val_loss: 0.0068 - val_mae: 0.0438 - learning_rate: 1.0000e-04
Epoch 27/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0049 - mae: 0.0217 - val_loss: 0.0056 - val_mae: 0.0333 - learning_rate: 1.0000e-04
Epoch 28/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0049 - mae: 0.0223 - val_loss: 0.0078 - val_mae: 0.0553 - learning_rate: 1.0000e-04
Epoch 29/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0048 - mae: 0.0224 - val_loss: 0.0083 - val_mae: 0.0590 - learning_rate: 1.0000e-04
Epoch 30/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0045 - mae: 0.0215 - val_loss: 0.0062 - val_mae: 0.0423 - learning_rate: 1.0000e-04
Epoch 31/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0043 - mae: 0.0202 - val_loss: 0.0060 - val_mae: 0.0429 - learning_rate: 1.0000e-04
Epoch 32/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0043 - mae: 0.0209 - val_loss: 0.0083 - val_mae: 0.0629 - learning_rate: 1.0000e-04
Epoch 33/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0043 - mae: 0.0221 - val_loss: 0.0062 - val_mae: 0.0479 - learning_rate: 1.0000e-04
Epoch 34/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0042 - mae: 0.0219 - val_loss: 0.0063 - val_mae: 0.0507 - learning_rate: 1.0000e-04
Epoch 35/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0038 - mae: 0.0204 - val_loss: 0.0061 - val_mae: 0.0480 - learning_rate: 1.0000e-04
Epoch 36/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0038 - mae: 0.0208 - val_loss: 0.0048 - val_mae: 0.0374 - learning_rate: 1.0000e-04
Epoch 37/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0037 - mae: 0.0196 - val_loss: 0.0047 - val_mae: 0.0353 - learning_rate: 1.0000e-04
Epoch 38/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0035 - mae: 0.0196 - val_loss: 0.0056 - val_mae: 0.0474 - learning_rate: 1.0000e-04
Epoch 39/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0037 - mae: 0.0213 - val_loss: 0.0049 - val_mae: 0.0405 - learning_rate: 1.0000e-04
Epoch 40/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0034 - mae: 0.0199 - val_loss: 0.0076 - val_mae: 0.0642 - learning_rate: 1.0000e-04
Epoch 41/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0032 - mae: 0.0193 - val_loss: 0.0053 - val_mae: 0.0465 - learning_rate: 1.0000e-04
Epoch 42/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0032 - mae: 0.0201 - val_loss: 0.0052 - val_mae: 0.0463 - learning_rate: 1.0000e-04
Epoch 43/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0031 - mae: 0.0195 - val_loss: 0.0037 - val_mae: 0.0319 - learning_rate: 1.0000e-04
Epoch 44/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0030 - mae: 0.0195 - val_loss: 0.0052 - val_mae: 0.0481 - learning_rate: 1.0000e-04
Epoch 45/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0029 - mae: 0.0192 - val_loss: 0.0044 - val_mae: 0.0420 - learning_rate: 1.0000e-04
Epoch 46/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0027 - mae: 0.0187 - val_loss: 0.0037 - val_mae: 0.0337 - learning_rate: 1.0000e-04
Epoch 47/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0028 - mae: 0.0200 - val_loss: 0.0050 - val_mae: 0.0480 - learning_rate: 1.0000e-04
Epoch 48/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0027 - mae: 0.0197 - val_loss: 0.0042 - val_mae: 0.0417 - learning_rate: 1.0000e-04
Epoch 49/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0026 - mae: 0.0197 - val_loss: 0.0040 - val_mae: 0.0396 - learning_rate: 1.0000e-04
Epoch 50/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0025 - mae: 0.0195 - val_loss: 0.0028 - val_mae: 0.0261 - learning_rate: 1.0000e-04
Epoch 51/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0026 - mae: 0.0195 - val_loss: 0.0031 - val_mae: 0.0315 - learning_rate: 1.0000e-04
Epoch 52/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0022 - mae: 0.0182 - val_loss: 0.0030 - val_mae: 0.0315 - learning_rate: 1.0000e-04
Epoch 53/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0023 - mae: 0.0187 - val_loss: 0.0024 - val_mae: 0.0229 - learning_rate: 1.0000e-04
Epoch 54/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0024 - mae: 0.0197 - val_loss: 0.0043 - val_mae: 0.0455 - learning_rate: 1.0000e-04
Epoch 55/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0021 - mae: 0.0178 - val_loss: 0.0026 - val_mae: 0.0282 - learning_rate: 1.0000e-04
Epoch 56/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0021 - mae: 0.0188 - val_loss: 0.0018 - val_mae: 0.0167 - learning_rate: 1.0000e-04
Epoch 57/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0020 - mae: 0.0188 - val_loss: 0.0022 - val_mae: 0.0250 - learning_rate: 1.0000e-04
Epoch 58/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0021 - mae: 0.0192 - val_loss: 0.0026 - val_mae: 0.0299 - learning_rate: 1.0000e-04
Epoch 59/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0020 - mae: 0.0191 - val_loss: 0.0021 - val_mae: 0.0239 - learning_rate: 1.0000e-04
Epoch 60/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0020 - mae: 0.0195 - val_loss: 0.0034 - val_mae: 0.0413 - learning_rate: 1.0000e-04
Restoring model weights from the end of the best epoch: 56.

Đánh giá mô hình 8:2¶

In [19]:
# Vẽ val_loss để đánh giá overfitting cho split 8:2
plt.figure(figsize=(12, 8))

plt.subplot(2, 1, 1)
plt.plot(history_rnn_82.history['loss'], label='Training Loss', linewidth=2)
plt.plot(history_rnn_82.history['val_loss'], label='Validation Loss', linewidth=2)
plt.title('RNN Model Loss 8:2 Split - Training vs Validation')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# Tìm epoch có val_loss thấp nhất cho split 8:2
best_epoch_82 = np.argmin(history_rnn_82.history['val_loss']) + 1
best_val_loss_82 = min(history_rnn_82.history['val_loss'])
print(f"Epoch tốt nhất (8:2): {best_epoch_82} với val_loss: {best_val_loss_82:.6f}")
No description has been provided for this image
Epoch tốt nhất (8:2): 56 với val_loss: 0.001776

Dự đoán và trực quan hóa 8:2¶

In [20]:
# Dự đoán 30, 60, 90 ngày tiếp theo cho split 8:2
last_data_scaled_82 = scaled_test_input_82[-time_step:]

forecasted_prices_30_82 = forecast_multivariate_prices(model_rnn_82, last_data_scaled_82, time_step, 30, scaler_target)
forecasted_prices_60_82 = forecast_multivariate_prices(model_rnn_82, last_data_scaled_82, time_step, 60, scaler_target)
forecasted_prices_90_82 = forecast_multivariate_prices(model_rnn_82, last_data_scaled_82, time_step, 90, scaler_target)

# Dự đoán giá trên tập kiểm tra cho split 8:2
test_predict_scaled_82 = model_rnn_82.predict(X_test_82)
test_predict_rnn_82 = scaler_target.inverse_transform(test_predict_scaled_82)

# Tạo DataFrame cho các dự đoán 8:2
forecast_dates_30_82 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=30, freq='D')
forecast_dates_60_82 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=60, freq='D')
forecast_dates_90_82 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=90, freq='D')

forecast_df_30_82 = pd.DataFrame(forecasted_prices_30_82, index=forecast_dates_30_82, columns=['Price'])
forecast_df_60_82 = pd.DataFrame(forecasted_prices_60_82, index=forecast_dates_60_82, columns=['Price'])
forecast_df_90_82 = pd.DataFrame(forecasted_prices_90_82, index=forecast_dates_90_82, columns=['Price'])

# Trực quan hóa kết quả cho split 8:2
plt.figure(figsize=(16, 10))

# Vẽ giá thực tế
plt.plot(data.index, data['Price'], label='Giá thực tế', color='blue', linewidth=2, alpha=0.8)

# Vẽ dự đoán trên tập test 8:2
plt.plot(test_data_82.index[time_step:], test_predict_rnn_82,
         label='Dự đoán trên tập test (8:2)', color='orange', linewidth=2, alpha=0.8)

# Vẽ các dự đoán tương lai 8:2
plt.plot(forecast_df_30_82.index, forecast_df_30_82['Price'],
         label='Dự đoán 30 ngày tiếp theo (8:2)', color='red', linestyle='--', linewidth=2, alpha=0.7)

plt.plot(forecast_df_60_82.index, forecast_df_60_82['Price'],
         label='Dự đoán 60 ngày tiếp theo (8:2)', color='green', linestyle='--', linewidth=2, alpha=0.5)

plt.plot(forecast_df_90_82.index, forecast_df_90_82['Price'],
         label='Dự đoán 90 ngày tiếp theo (8:2)', color='purple', linestyle='--', linewidth=2, alpha=0.3)

# Thêm đường thẳng đứng để phân biệt vùng dữ liệu
plt.axvline(x=data.index[-1], color='black', linestyle=':', alpha=0.7,
            label='Ngày cuối cùng của dữ liệu thực tế')

plt.title(f'Dự đoán giá Bitcoin bằng RNN (8:2 Split, Time Step = {time_step})\nDự đoán 30, 60, 90 ngày tiếp theo',
          fontsize=16, fontweight='bold')
plt.xlabel('Ngày', fontsize=12)
plt.ylabel('Giá (USD)', fontsize=12)
plt.legend(fontsize=10, loc='upper left')
plt.grid(True, alpha=0.3)

plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
20/20 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step
No description has been provided for this image
In [21]:
# Đánh giá mô hình 8:2
# Lấy giá trị thực tế trên tập test 8:2
y_test_actual_82 = test_data_82['Price'].values[time_step:]

# Tính toán các metrics cho split 8:2
mape_82 = mean_absolute_percentage_error(y_test_actual_82, test_predict_rnn_82.flatten())
mse_82 = mean_squared_error(y_test_actual_82, test_predict_rnn_82.flatten())
rmse_82 = np.sqrt(mse_82)

print(f'Kết quả đánh giá mô hình RNN 8:2 Split (Time Step = {time_step}):')
print(f'MAPE: {mape_82:.2f}%')
print(f'MSE: {mse_82:.2f}')
print(f'RMSE: {rmse_82:.2f}')
print(f'Số epochs huấn luyện: {len(history_rnn_82.history["loss"])}')

# Hiển thị thông tin dự đoán 30 ngày cho 8:2
print(f'\nDự đoán giá Bitcoin 30 ngày tiếp theo (8:2):')
print(f'Giá cao nhất: ${forecasted_prices_30_82.max():.2f}')
print(f'Giá thấp nhất: ${forecasted_prices_30_82.min():.2f}')
print(f'Giá trung bình: ${forecasted_prices_30_82.mean():.2f}')
Kết quả đánh giá mô hình RNN 8:2 Split (Time Step = 50):
MAPE: 0.03%
MSE: 6470138.82
RMSE: 2543.65
Số epochs huấn luyện: 60

Dự đoán giá Bitcoin 30 ngày tiếp theo (8:2):
Giá cao nhất: $103510.48
Giá thấp nhất: $101639.00
Giá trung bình: $102173.93

Chia 9:1¶

Chuẩn hóa dữ liệu 9:1¶

In [22]:
# Chuẩn hóa dữ liệu
# Lấy 3 cột Price, Open, Vol. để làm đầu vào và Price làm đầu ra
input_features = data[['Price', 'Open', 'Vol.']].values
target_feature = data[['Price']].values

# Áp dụng MinMaxScaler cho input features
scaler_input = MinMaxScaler(feature_range=(0, 1))
scaled_input = scaler_input.fit_transform(input_features)

# Áp dụng MinMaxScaler cho target feature
scaler_target = MinMaxScaler(feature_range=(0, 1))
scaled_target = scaler_target.fit_transform(target_feature)
In [23]:
# Chia dữ liệu train/test theo tỷ lệ 9:1
train_size_91 = int(len(data) * 0.9)
train_data_91 = data.iloc[0:train_size_91,:]
test_data_91 = data.iloc[train_size_91:len(data),:]

# Chia dữ liệu đã chuẩn hóa
scaled_train_input_91 = scaled_input[0:train_size_91,:]
scaled_test_input_91 = scaled_input[train_size_91:,:]
scaled_train_target_91 = scaled_target[0:train_size_91,:]
scaled_test_target_91 = scaled_target[train_size_91:,:]

print(f"Kích thước tập train 9:1: {len(train_data_91)}")
print(f"Kích thước tập test 9:1: {len(test_data_91)}")
Kích thước tập train 9:1: 3033
Kích thước tập test 9:1: 337

Xây dựng mô hình RNN¶

In [24]:
def build_rnn_model_with_regularization(time_step: int, num_features: int) -> Sequential:
    """
    Xây dựng mô hình RNN với regularization

    Args:
        time_step: Số time steps để nhìn về quá khứ
        num_features: Số features đầu vào (Price, Open, Vol = 3)

    Returns:
        Sequential model
    """
    model = Sequential()

    # SimpleRNN layer với regularization
    model.add(SimpleRNN(
        units=50,                       # Số neurons
        input_shape=(time_step, num_features),  # (50, 3)
        kernel_regularizer=regularizers.l2(0.001),    # L2 regularization
        return_sequences=False           # Chỉ cần output cuối cùng
    ))

    # Dropout để tránh overfitting
    model.add(Dropout(0.3))

    # Dense layer ẩn
    model.add(Dense(32, activation='relu'))
    model.add(Dropout(0.2))

    # Output layer: dự đoán 1 giá trị (Price)
    model.add(Dense(1))

    # Optimizer với learning rate nhỏ
    optimizer = Adam(learning_rate=1e-4)
    model.compile(optimizer=optimizer, loss='mean_squared_error', metrics=['mae'])

    return model
In [25]:
# Hàm tạo dữ liệu time series với multiple features
def create_multivariate_time_series_data(input_data: np.ndarray, target_data: np.ndarray, time_step: int) -> Tuple[np.ndarray, np.ndarray]:
    X_data, y_data = [], []
    for i in range(len(input_data) - time_step):
        X_data.append(input_data[i:(i + time_step), :])  # Lấy tất cả features
        y_data.append(target_data[i + time_step, 0])     # Chỉ lấy Price
    return np.array(X_data), np.array(y_data)
In [26]:
# Hàm dự đoán với multiple features
def forecast_multivariate_prices(model: Sequential, input_data: np.ndarray, time_step: int,
                                forecast_days: int, scaler_target: MinMaxScaler) -> np.ndarray:
    temp_input = input_data[-time_step:].reshape(1, time_step, input_data.shape[1])
    lst_output = []

    for _ in range(forecast_days):
        predicted_price = model.predict(temp_input, verbose=0)
        lst_output.append(predicted_price[0].tolist())

        # Tạo input mới cho prediction tiếp theo
        # Giả sử các features khác không đổi, chỉ cập nhật Price
        new_row = temp_input[0, -1, :].copy()
        new_row[0] = predicted_price[0, 0]  # Cập nhật Price prediction

        temp_input = np.append(temp_input[:, 1:, :],
                              new_row.reshape(1, 1, input_data.shape[1]), axis=1)

    # Chuyển đổi lst_output thành numpy array và inverse transform
    lst_output = np.array(lst_output).reshape(-1, 1)
    return scaler_target.inverse_transform(lst_output)

Huấn luyện mô hình 9:1¶

In [27]:
# Tạo dữ liệu train và test với time_step = 50 cho split 9:1
X_train_91, y_train_91 = create_multivariate_time_series_data(scaled_train_input_91, scaled_train_target_91, time_step)
X_test_91, y_test_91 = create_multivariate_time_series_data(scaled_test_input_91, scaled_test_target_91, time_step)

print(f"X_train_91 shape: {X_train_91.shape}")
print(f"y_train_91 shape: {y_train_91.shape}")
print(f"X_test_91 shape: {X_test_91.shape}")
print(f"y_test_91 shape: {y_test_91.shape}")

# Xây dựng mô hình RNN cho split 9:1
model_rnn_91 = build_rnn_model_with_regularization(time_step, 3)  # 3 features: Price, Open, Vol

# Callbacks để tối ưu hóa
early_stop_91 = EarlyStopping(monitor='val_loss', patience=15, restore_best_weights=True, verbose=1)
reduce_lr_91 = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=10, min_lr=1e-7, verbose=1)

# Huấn luyện mô hình 9:1
history_rnn_91 = model_rnn_91.fit(
    X_train_91, y_train_91,
    epochs=60,
    batch_size=16,
    validation_data=(X_test_91, y_test_91),
    callbacks=[early_stop_91, reduce_lr_91],
    verbose=1
)
X_train_91 shape: (2983, 50, 3)
y_train_91 shape: (2983,)
X_test_91 shape: (287, 50, 3)
y_test_91 shape: (287,)
Epoch 1/60
c:\Users\Hii\AppData\Local\Programs\Python\Python311\Lib\site-packages\keras\src\layers\rnn\rnn.py:199: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead.
  super().__init__(**kwargs)
187/187 ━━━━━━━━━━━━━━━━━━━━ 3s 6ms/step - loss: 0.0316 - mae: 0.0923 - val_loss: 0.0410 - val_mae: 0.1767 - learning_rate: 1.0000e-04
Epoch 2/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0135 - mae: 0.0525 - val_loss: 0.0303 - val_mae: 0.1472 - learning_rate: 1.0000e-04
Epoch 3/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0124 - mae: 0.0485 - val_loss: 0.0157 - val_mae: 0.0901 - learning_rate: 1.0000e-04
Epoch 4/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0104 - mae: 0.0421 - val_loss: 0.0172 - val_mae: 0.0979 - learning_rate: 1.0000e-04
Epoch 5/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0093 - mae: 0.0383 - val_loss: 0.0131 - val_mae: 0.0776 - learning_rate: 1.0000e-04
Epoch 6/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0088 - mae: 0.0353 - val_loss: 0.0119 - val_mae: 0.0729 - learning_rate: 1.0000e-04
Epoch 7/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0086 - mae: 0.0348 - val_loss: 0.0104 - val_mae: 0.0647 - learning_rate: 1.0000e-04
Epoch 8/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0082 - mae: 0.0335 - val_loss: 0.0101 - val_mae: 0.0646 - learning_rate: 1.0000e-04
Epoch 9/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0075 - mae: 0.0315 - val_loss: 0.0116 - val_mae: 0.0752 - learning_rate: 1.0000e-04
Epoch 10/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0073 - mae: 0.0305 - val_loss: 0.0076 - val_mae: 0.0444 - learning_rate: 1.0000e-04
Epoch 11/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0074 - mae: 0.0317 - val_loss: 0.0073 - val_mae: 0.0444 - learning_rate: 1.0000e-04
Epoch 12/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0068 - mae: 0.0289 - val_loss: 0.0075 - val_mae: 0.0481 - learning_rate: 1.0000e-04
Epoch 13/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0068 - mae: 0.0290 - val_loss: 0.0070 - val_mae: 0.0442 - learning_rate: 1.0000e-04
Epoch 14/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0065 - mae: 0.0289 - val_loss: 0.0081 - val_mae: 0.0548 - learning_rate: 1.0000e-04
Epoch 15/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0062 - mae: 0.0276 - val_loss: 0.0056 - val_mae: 0.0302 - learning_rate: 1.0000e-04
Epoch 16/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0063 - mae: 0.0286 - val_loss: 0.0054 - val_mae: 0.0296 - learning_rate: 1.0000e-04
Epoch 17/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0061 - mae: 0.0277 - val_loss: 0.0061 - val_mae: 0.0386 - learning_rate: 1.0000e-04
Epoch 18/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0061 - mae: 0.0283 - val_loss: 0.0054 - val_mae: 0.0329 - learning_rate: 1.0000e-04
Epoch 19/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0058 - mae: 0.0271 - val_loss: 0.0059 - val_mae: 0.0404 - learning_rate: 1.0000e-04
Epoch 20/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0055 - mae: 0.0260 - val_loss: 0.0060 - val_mae: 0.0418 - learning_rate: 1.0000e-04
Epoch 21/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0054 - mae: 0.0259 - val_loss: 0.0053 - val_mae: 0.0356 - learning_rate: 1.0000e-04
Epoch 22/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0051 - mae: 0.0249 - val_loss: 0.0042 - val_mae: 0.0215 - learning_rate: 1.0000e-04
Epoch 23/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0052 - mae: 0.0267 - val_loss: 0.0047 - val_mae: 0.0302 - learning_rate: 1.0000e-04
Epoch 24/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0047 - mae: 0.0239 - val_loss: 0.0044 - val_mae: 0.0283 - learning_rate: 1.0000e-04
Epoch 25/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0047 - mae: 0.0242 - val_loss: 0.0045 - val_mae: 0.0305 - learning_rate: 1.0000e-04
Epoch 26/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0048 - mae: 0.0257 - val_loss: 0.0046 - val_mae: 0.0335 - learning_rate: 1.0000e-04
Epoch 27/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0047 - mae: 0.0252 - val_loss: 0.0045 - val_mae: 0.0337 - learning_rate: 1.0000e-04
Epoch 28/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0044 - mae: 0.0242 - val_loss: 0.0044 - val_mae: 0.0333 - learning_rate: 1.0000e-04
Epoch 29/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0042 - mae: 0.0234 - val_loss: 0.0042 - val_mae: 0.0318 - learning_rate: 1.0000e-04
Epoch 30/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0041 - mae: 0.0226 - val_loss: 0.0037 - val_mae: 0.0259 - learning_rate: 1.0000e-04
Epoch 31/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0040 - mae: 0.0240 - val_loss: 0.0056 - val_mae: 0.0495 - learning_rate: 1.0000e-04
Epoch 32/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0037 - mae: 0.0225 - val_loss: 0.0035 - val_mae: 0.0255 - learning_rate: 1.0000e-04
Epoch 33/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0036 - mae: 0.0220 - val_loss: 0.0056 - val_mae: 0.0511 - learning_rate: 1.0000e-04
Epoch 34/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0036 - mae: 0.0227 - val_loss: 0.0036 - val_mae: 0.0293 - learning_rate: 1.0000e-04
Epoch 35/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0038 - mae: 0.0241 - val_loss: 0.0044 - val_mae: 0.0413 - learning_rate: 1.0000e-04
Epoch 36/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0035 - mae: 0.0230 - val_loss: 0.0030 - val_mae: 0.0224 - learning_rate: 1.0000e-04
Epoch 37/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0032 - mae: 0.0224 - val_loss: 0.0051 - val_mae: 0.0493 - learning_rate: 1.0000e-04
Epoch 38/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0033 - mae: 0.0230 - val_loss: 0.0026 - val_mae: 0.0189 - learning_rate: 1.0000e-04
Epoch 39/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0030 - mae: 0.0214 - val_loss: 0.0062 - val_mae: 0.0603 - learning_rate: 1.0000e-04
Epoch 40/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0032 - mae: 0.0230 - val_loss: 0.0027 - val_mae: 0.0234 - learning_rate: 1.0000e-04
Epoch 41/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0029 - mae: 0.0218 - val_loss: 0.0041 - val_mae: 0.0419 - learning_rate: 1.0000e-04
Epoch 42/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0029 - mae: 0.0222 - val_loss: 0.0049 - val_mae: 0.0504 - learning_rate: 1.0000e-04
Epoch 43/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0028 - mae: 0.0218 - val_loss: 0.0036 - val_mae: 0.0375 - learning_rate: 1.0000e-04
Epoch 44/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0029 - mae: 0.0224 - val_loss: 0.0030 - val_mae: 0.0313 - learning_rate: 1.0000e-04
Epoch 45/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0027 - mae: 0.0218 - val_loss: 0.0033 - val_mae: 0.0352 - learning_rate: 1.0000e-04
Epoch 46/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0025 - mae: 0.0203 - val_loss: 0.0036 - val_mae: 0.0400 - learning_rate: 1.0000e-04
Epoch 47/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0026 - mae: 0.0221 - val_loss: 0.0041 - val_mae: 0.0452 - learning_rate: 1.0000e-04
Epoch 48/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.0025 - mae: 0.0216
Epoch 48: ReduceLROnPlateau reducing learning rate to 4.999999873689376e-05.
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0025 - mae: 0.0216 - val_loss: 0.0038 - val_mae: 0.0430 - learning_rate: 1.0000e-04
Epoch 49/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0026 - mae: 0.0225 - val_loss: 0.0034 - val_mae: 0.0383 - learning_rate: 5.0000e-05
Epoch 50/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0024 - mae: 0.0215 - val_loss: 0.0044 - val_mae: 0.0495 - learning_rate: 5.0000e-05
Epoch 51/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0023 - mae: 0.0209 - val_loss: 0.0035 - val_mae: 0.0405 - learning_rate: 5.0000e-05
Epoch 52/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0024 - mae: 0.0208 - val_loss: 0.0030 - val_mae: 0.0351 - learning_rate: 5.0000e-05
Epoch 53/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0023 - mae: 0.0214 - val_loss: 0.0033 - val_mae: 0.0393 - learning_rate: 5.0000e-05
Epoch 53: early stopping
Restoring model weights from the end of the best epoch: 38.

Đánh giá mô hình 9:1¶

In [28]:
# Vẽ val_loss để đánh giá overfitting cho split 9:1
plt.figure(figsize=(12, 8))

plt.subplot(2, 1, 1)
plt.plot(history_rnn_91.history['loss'], label='Training Loss', linewidth=2)
plt.plot(history_rnn_91.history['val_loss'], label='Validation Loss', linewidth=2)
plt.title('RNN Model Loss 9:1 Split - Training vs Validation')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# Tìm epoch có val_loss thấp nhất cho split 9:1
best_epoch_91 = np.argmin(history_rnn_91.history['val_loss']) + 1
best_val_loss_91 = min(history_rnn_91.history['val_loss'])
print(f"Epoch tốt nhất (9:1): {best_epoch_91} với val_loss: {best_val_loss_91:.6f}")
No description has been provided for this image
Epoch tốt nhất (9:1): 38 với val_loss: 0.002644

Dự đoán và trực quan hóa 9:1¶

In [29]:
# Dự đoán 30, 60, 90 ngày tiếp theo cho split 9:1
last_data_scaled_91 = scaled_test_input_91[-time_step:]

forecasted_prices_30_91 = forecast_multivariate_prices(model_rnn_91, last_data_scaled_91, time_step, 30, scaler_target)
forecasted_prices_60_91 = forecast_multivariate_prices(model_rnn_91, last_data_scaled_91, time_step, 60, scaler_target)
forecasted_prices_90_91 = forecast_multivariate_prices(model_rnn_91, last_data_scaled_91, time_step, 90, scaler_target)

# Dự đoán giá trên tập kiểm tra cho split 9:1
test_predict_scaled_91 = model_rnn_91.predict(X_test_91)
test_predict_rnn_91 = scaler_target.inverse_transform(test_predict_scaled_91)

# Tạo DataFrame cho các dự đoán 9:1
forecast_dates_30_91 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=30, freq='D')
forecast_dates_60_91 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=60, freq='D')
forecast_dates_90_91 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=90, freq='D')

forecast_df_30_91 = pd.DataFrame(forecasted_prices_30_91, index=forecast_dates_30_91, columns=['Price'])
forecast_df_60_91 = pd.DataFrame(forecasted_prices_60_91, index=forecast_dates_60_91, columns=['Price'])
forecast_df_90_91 = pd.DataFrame(forecasted_prices_90_91, index=forecast_dates_90_91, columns=['Price'])

# Trực quan hóa kết quả cho split 9:1
plt.figure(figsize=(16, 10))

# Vẽ giá thực tế
plt.plot(data.index, data['Price'], label='Giá thực tế', color='blue', linewidth=2, alpha=0.8)

# Vẽ dự đoán trên tập test 9:1
plt.plot(test_data_91.index[time_step:], test_predict_rnn_91,
         label='Dự đoán trên tập test (9:1)', color='orange', linewidth=2, alpha=0.8)

# Vẽ các dự đoán tương lai 9:1
plt.plot(forecast_df_30_91.index, forecast_df_30_91['Price'],
         label='Dự đoán 30 ngày tiếp theo (9:1)', color='red', linestyle='--', linewidth=2, alpha=0.7)

plt.plot(forecast_df_60_91.index, forecast_df_60_91['Price'],
         label='Dự đoán 60 ngày tiếp theo (9:1)', color='green', linestyle='--', linewidth=2, alpha=0.5)

plt.plot(forecast_df_90_91.index, forecast_df_90_91['Price'],
         label='Dự đoán 90 ngày tiếp theo (9:1)', color='purple', linestyle='--', linewidth=2, alpha=0.3)

# Thêm đường thẳng đứng để phân biệt vùng dữ liệu
plt.axvline(x=data.index[-1], color='black', linestyle=':', alpha=0.7,
            label='Ngày cuối cùng của dữ liệu thực tế')

plt.title(f'Dự đoán giá Bitcoin bằng RNN (9:1 Split, Time Step = {time_step})\nDự đoán 30, 60, 90 ngày tiếp theo',
          fontsize=16, fontweight='bold')
plt.xlabel('Ngày', fontsize=12)
plt.ylabel('Giá (USD)', fontsize=12)
plt.legend(fontsize=10, loc='upper left')
plt.grid(True, alpha=0.3)

plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
9/9 ━━━━━━━━━━━━━━━━━━━━ 0s 5ms/step  
No description has been provided for this image
In [30]:
# Đánh giá mô hình 9:1
# Lấy giá trị thực tế trên tập test 9:1
y_test_actual_91 = test_data_91['Price'].values[time_step:]

# Tính toán các metrics cho split 9:1
mape_91 = mean_absolute_percentage_error(y_test_actual_91, test_predict_rnn_91.flatten())
mse_91 = mean_squared_error(y_test_actual_91, test_predict_rnn_91.flatten())
rmse_91 = np.sqrt(mse_91)

print(f'Kết quả đánh giá mô hình RNN 9:1 Split (Time Step = {time_step}):')
print(f'MAPE: {mape_91:.2f}%')
print(f'MSE: {mse_91:.2f}')
print(f'RMSE: {rmse_91:.2f}')
print(f'Số epochs huấn luyện: {len(history_rnn_91.history["loss"])}')

# Hiển thị thông tin dự đoán 30 ngày cho 9:1
print(f'\nDự đoán giá Bitcoin 30 ngày tiếp theo (9:1):')
print(f'Giá cao nhất: ${forecasted_prices_30_91.max():.2f}')
print(f'Giá thấp nhất: ${forecasted_prices_30_91.min():.2f}')
print(f'Giá trung bình: ${forecasted_prices_30_91.mean():.2f}')
Kết quả đánh giá mô hình RNN 9:1 Split (Time Step = 50):
MAPE: 0.02%
MSE: 7844551.54
RMSE: 2800.81
Số epochs huấn luyện: 53

Dự đoán giá Bitcoin 30 ngày tiếp theo (9:1):
Giá cao nhất: $103320.61
Giá thấp nhất: $68552.92
Giá trung bình: $85429.66

So sánh 3 tỉ lệ¶

In [31]:
# So sánh chi tiết giữa 3 tỉ lệ chia dữ liệu
print("="*80)
print("SO SÁNH CHI TIẾT GIỮA 3 TỈ LỆ CHIA DỮ LIỆU")
print("="*80)

# Thu thập thông tin từ 3 splits
splits_info = {
    '7:3': {
        'train_size': len(train_data),
        'test_size': len(test_data),
        'mape': mape,
        'mse': mse,
        'rmse': rmse,
        'epochs': len(history_rnn.history['loss']),
        'best_val_loss': best_val_loss,
        'best_epoch': best_epoch,
        'final_train_loss': history_rnn.history['loss'][-1],
        'final_val_loss': history_rnn.history['val_loss'][-1]
    },
    '8:2': {
        'train_size': len(train_data_82),
        'test_size': len(test_data_82),
        'mape': mape_82,
        'mse': mse_82,
        'rmse': rmse_82,
        'epochs': len(history_rnn_82.history['loss']),
        'best_val_loss': best_val_loss_82,
        'best_epoch': best_epoch_82,
        'final_train_loss': history_rnn_82.history['loss'][-1],
        'final_val_loss': history_rnn_82.history['val_loss'][-1]
    },
    '9:1': {
        'train_size': len(train_data_91),
        'test_size': len(test_data_91),
        'mape': mape_91,
        'mse': mse_91,
        'rmse': rmse_91,
        'epochs': len(history_rnn_91.history['loss']),
        'best_val_loss': best_val_loss_91,
        'best_epoch': best_epoch_91,
        'final_train_loss': history_rnn_91.history['loss'][-1],
        'final_val_loss': history_rnn_91.history['val_loss'][-1]
    }
}

# In bảng so sánh
for split, info in splits_info.items():
    print(f"\n{split} Split:")
    print(f"  Kích thước train: {info['train_size']:,} mẫu")
    print(f"  Kích thước test: {info['test_size']:,} mẫu")
    print(f"  MAPE: {info['mape']:.2f}%")
    print(f"  MSE: {info['mse']:,.2f}")
    print(f"  RMSE: {info['rmse']:,.2f}")
    print(f"  Số epochs: {info['epochs']}")
    print(f"  Best val_loss: {info['best_val_loss']:.6f} (epoch {info['best_epoch']})")
    print(f"  Final train_loss: {info['final_train_loss']:.6f}")
    print(f"  Final val_loss: {info['final_val_loss']:.6f}")
    print(f"  Overfitting gap: {abs(info['final_val_loss'] - info['final_train_loss']):.6f}")
================================================================================
SO SÁNH CHI TIẾT GIỮA 3 TỈ LỆ CHIA DỮ LIỆU
================================================================================

7:3 Split:
  Kích thước train: 2,359 mẫu
  Kích thước test: 1,011 mẫu
  MAPE: 0.03%
  MSE: 5,527,311.55
  RMSE: 2,351.02
  Số epochs: 60
  Best val_loss: 0.001849 (epoch 60)
  Final train_loss: 0.002247
  Final val_loss: 0.001849
  Overfitting gap: 0.000399

8:2 Split:
  Kích thước train: 2,696 mẫu
  Kích thước test: 674 mẫu
  MAPE: 0.03%
  MSE: 6,470,138.82
  RMSE: 2,543.65
  Số epochs: 60
  Best val_loss: 0.001776 (epoch 56)
  Final train_loss: 0.001935
  Final val_loss: 0.003447
  Overfitting gap: 0.001512

9:1 Split:
  Kích thước train: 3,033 mẫu
  Kích thước test: 337 mẫu
  MAPE: 0.02%
  MSE: 7,844,551.54
  RMSE: 2,800.81
  Số epochs: 53
  Best val_loss: 0.002644 (epoch 38)
  Final train_loss: 0.002328
  Final val_loss: 0.003336
  Overfitting gap: 0.001008
In [32]:
# Vẽ biểu đồ so sánh các metrics
fig, axes = plt.subplots(2, 3, figsize=(18, 12))

splits = ['7:3', '8:2', '9:1']
colors = ['#1f77b4', '#ff7f0e', '#2ca02c']

# 1. So sánh MAPE
mape_values = [splits_info[split]['mape'] for split in splits]
axes[0, 0].bar(splits, mape_values, color=colors, alpha=0.7)
axes[0, 0].set_title('So sánh MAPE (%)', fontsize=14, fontweight='bold')
axes[0, 0].set_ylabel('MAPE (%)')
axes[0, 0].grid(True, alpha=0.3)
for i, v in enumerate(mape_values):
    axes[0, 0].text(i, v + 0.1, f'{v:.2f}%', ha='center', va='bottom', fontweight='bold')

# 2. So sánh RMSE
rmse_values = [splits_info[split]['rmse'] for split in splits]
axes[0, 1].bar(splits, rmse_values, color=colors, alpha=0.7)
axes[0, 1].set_title('So sánh RMSE (USD)', fontsize=14, fontweight='bold')
axes[0, 1].set_ylabel('RMSE (USD)')
axes[0, 1].grid(True, alpha=0.3)
for i, v in enumerate(rmse_values):
    axes[0, 1].text(i, v + 200, f'{v:,.0f}', ha='center', va='bottom', fontweight='bold')

# 3. So sánh Best Validation Loss
best_val_loss_values = [splits_info[split]['best_val_loss'] for split in splits]
axes[0, 2].bar(splits, best_val_loss_values, color=colors, alpha=0.7)
axes[0, 2].set_title('So sánh Best Validation Loss', fontsize=14, fontweight='bold')
axes[0, 2].set_ylabel('Best Val Loss')
axes[0, 2].grid(True, alpha=0.3)
for i, v in enumerate(best_val_loss_values):
    axes[0, 2].text(i, v + 0.0001, f'{v:.4f}', ha='center', va='bottom', fontweight='bold')

# 4. So sánh số epochs
epochs_values = [splits_info[split]['epochs'] for split in splits]
axes[1, 0].bar(splits, epochs_values, color=colors, alpha=0.7)
axes[1, 0].set_title('So sánh Số Epochs', fontsize=14, fontweight='bold')
axes[1, 0].set_ylabel('Số Epochs')
axes[1, 0].grid(True, alpha=0.3)
for i, v in enumerate(epochs_values):
    axes[1, 0].text(i, v + 0.5, f'{v}', ha='center', va='bottom', fontweight='bold')

# 5. So sánh Overfitting Gap
overfitting_gap = [abs(splits_info[split]['final_val_loss'] - splits_info[split]['final_train_loss']) 
                   for split in splits]
axes[1, 1].bar(splits, overfitting_gap, color=colors, alpha=0.7)
axes[1, 1].set_title('So sánh Overfitting Gap', fontsize=14, fontweight='bold')
axes[1, 1].set_ylabel('|Val Loss - Train Loss|')
axes[1, 1].grid(True, alpha=0.3)
for i, v in enumerate(overfitting_gap):
    axes[1, 1].text(i, v + 0.001, f'{v:.4f}', ha='center', va='bottom', fontweight='bold')

# 6. So sánh kích thước test set
test_sizes = [splits_info[split]['test_size'] for split in splits]
axes[1, 2].bar(splits, test_sizes, color=colors, alpha=0.7)
axes[1, 2].set_title('So sánh Kích thước Test Set', fontsize=14, fontweight='bold')
axes[1, 2].set_ylabel('Số mẫu test')
axes[1, 2].grid(True, alpha=0.3)
for i, v in enumerate(test_sizes):
    axes[1, 2].text(i, v + 20, f'{v:,}', ha='center', va='bottom', fontweight='bold')

plt.tight_layout()
plt.show()
C:\Users\Hii\AppData\Local\Temp\ipykernel_28116\2283599594.py:62: UserWarning: Tight layout not applied. The bottom and top margins cannot be made large enough to accommodate all Axes decorations.
  plt.tight_layout()
No description has been provided for this image
In [33]:
# Vẽ so sánh Training và Validation Loss curves
plt.figure(figsize=(18, 6))

# Subplot 1: 7:3 Split
plt.subplot(1, 3, 1)
plt.plot(history_rnn.history['loss'], label='Training Loss', linewidth=2, color='blue')
plt.plot(history_rnn.history['val_loss'], label='Validation Loss', linewidth=2, color='red')
plt.title('7:3 Split - Loss Curves', fontsize=14, fontweight='bold')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.axvline(x=best_epoch-1, color='green', linestyle='--', alpha=0.7, label=f'Best Epoch: {best_epoch}')

# Subplot 2: 8:2 Split
plt.subplot(1, 3, 2)
plt.plot(history_rnn_82.history['loss'], label='Training Loss', linewidth=2, color='blue')
plt.plot(history_rnn_82.history['val_loss'], label='Validation Loss', linewidth=2, color='red')
plt.title('8:2 Split - Loss Curves', fontsize=14, fontweight='bold')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.axvline(x=best_epoch_82-1, color='green', linestyle='--', alpha=0.7, label=f'Best Epoch: {best_epoch_82}')

# Subplot 3: 9:1 Split
plt.subplot(1, 3, 3)
plt.plot(history_rnn_91.history['loss'], label='Training Loss', linewidth=2, color='blue')
plt.plot(history_rnn_91.history['val_loss'], label='Validation Loss', linewidth=2, color='red')
plt.title('9:1 Split - Loss Curves', fontsize=14, fontweight='bold')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.axvline(x=best_epoch_91-1, color='green', linestyle='--', alpha=0.7, label=f'Best Epoch: {best_epoch_91}')

plt.tight_layout()
plt.show()
No description has been provided for this image
In [34]:
# Tạo DataFrame tổng hợp kết quả để dễ so sánh
import pandas as pd

comparison_df = pd.DataFrame({
    'Split': ['7:3', '8:2', '9:1'],
    'Train_Size': [splits_info[split]['train_size'] for split in splits],
    'Test_Size': [splits_info[split]['test_size'] for split in splits],
    'MAPE (%)': [splits_info[split]['mape'] for split in splits],
    'RMSE (USD)': [splits_info[split]['rmse'] for split in splits],
    'MSE': [splits_info[split]['mse'] for split in splits],
    'Best_Val_Loss': [splits_info[split]['best_val_loss'] for split in splits],
    'Best_Epoch': [splits_info[split]['best_epoch'] for split in splits],
    'Total_Epochs': [splits_info[split]['epochs'] for split in splits],
    'Overfitting_Gap': [abs(splits_info[split]['final_val_loss'] - splits_info[split]['final_train_loss']) 
                        for split in splits]
})

print("\nBẢNG TỔNG HỢP KẾT QUẢ:")
print("="*100)
print(comparison_df.to_string(index=False, float_format='%.4f'))
BẢNG TỔNG HỢP KẾT QUẢ:
====================================================================================================
Split  Train_Size  Test_Size  MAPE (%)  RMSE (USD)          MSE  Best_Val_Loss  Best_Epoch  Total_Epochs  Overfitting_Gap
  7:3        2359       1011    0.0297   2351.0235 5527311.5494         0.0018          60            60           0.0004
  8:2        2696        674    0.0275   2543.6468 6470138.8172         0.0018          56            60           0.0015
  9:1        3033        337    0.0248   2800.8127 7844551.5441         0.0026          38            53           0.0010
In [35]:
# Phân tích và đưa ra khuyến nghị
print("\n" + "="*80)
print("PHÂN TÍCH VÀ KHUYẾN NGHỊ")
print("="*80)

# Tìm split tốt nhất cho từng metric
best_mape_split = splits[np.argmin([splits_info[split]['mape'] for split in splits])]
best_rmse_split = splits[np.argmin([splits_info[split]['rmse'] for split in splits])]
best_val_loss_split = splits[np.argmin([splits_info[split]['best_val_loss'] for split in splits])]
best_overfitting_split = splits[np.argmin([abs(splits_info[split]['final_val_loss'] - splits_info[split]['final_train_loss']) 
                                          for split in splits])]

print(f"\n1. PHÂN TÍCH THEO TỪNG TIÊU CHÍ:")
print(f"   • Tốt nhất theo MAPE: {best_mape_split} ({splits_info[best_mape_split]['mape']:.2f}%)")
print(f"   • Tốt nhất theo RMSE: {best_rmse_split} ({splits_info[best_rmse_split]['rmse']:,.2f} USD)")
print(f"   • Tốt nhất theo Val Loss: {best_val_loss_split} ({splits_info[best_val_loss_split]['best_val_loss']:.6f})")
print(f"   • Ít overfitting nhất: {best_overfitting_split} (gap: {abs(splits_info[best_overfitting_split]['final_val_loss'] - splits_info[best_overfitting_split]['final_train_loss']):.6f})")

# Tính điểm tổng hợp (rank-based scoring)
def calculate_rank_score(splits_info):
    scores = {}
    splits_list = list(splits_info.keys())
    
    # Rank cho MAPE (thấp hơn = tốt hơn)
    mape_rank = sorted(splits_list, key=lambda x: splits_info[x]['mape'])
    # Rank cho RMSE (thấp hơn = tốt hơn)
    rmse_rank = sorted(splits_list, key=lambda x: splits_info[x]['rmse'])
    # Rank cho Best Val Loss (thấp hơn = tốt hơn)
    val_loss_rank = sorted(splits_list, key=lambda x: splits_info[x]['best_val_loss'])
    # Rank cho Overfitting Gap (thấp hơn = tốt hơn)
    overfitting_rank = sorted(splits_list, key=lambda x: abs(splits_info[x]['final_val_loss'] - splits_info[x]['final_train_loss']))
    
    for split in splits_list:
        # Điểm rank (1 = tốt nhất, 3 = kém nhất)
        score = (mape_rank.index(split) + 1) * 0.3 + \
                (rmse_rank.index(split) + 1) * 0.3 + \
                (val_loss_rank.index(split) + 1) * 0.25 + \
                (overfitting_rank.index(split) + 1) * 0.15
        scores[split] = score
    
    return scores

# Tính điểm tổng hợp
scores = calculate_rank_score(splits_info)
best_overall_split = min(scores, key=scores.get)
print(f"\n2. ĐIỂM TỔNG HỢP (trọng số: MAPE=30%, RMSE=30%, Val_Loss=25%, Overfitting=15%):")
for split in splits_info.keys():
    print(f"   • {split}: {scores[split]:.2f} điểm")
    print(f"   • {split}: {scores[split]:.2f} điểm")

print(f"\n3. KẾT LUẬN VÀ KHUYẾN NGHỊ:")
print(f"   🏆 MÔ HÌNH TỐT NHẤT: Split {best_overall_split}")
print(f"   📊 Lý do:")
print(f"      - MAPE: {splits_info[best_overall_split]['mape']:.2f}%")
print(f"      - RMSE: {splits_info[best_overall_split]['rmse']:,.2f} USD")
print(f"      - Best Val Loss: {splits_info[best_overall_split]['best_val_loss']:.6f}")
print(f"      - Overfitting Gap: {abs(splits_info[best_overall_split]['final_val_loss'] - splits_info[best_overall_split]['final_train_loss']):.6f}")
print(f"      - Tập test có {splits_info[best_overall_split]['test_size']:,} mẫu (đủ để đánh giá)")
print(f"      - Huấn luyện ổn định với {splits_info[best_overall_split]['epochs']} epochs")

print(f"\n4. NHẬN XÉT CHUNG:")
if best_overall_split == '7:3':
    print("   • Split 7:3 cân bằng tốt giữa kích thước tập train và test")
    print("   • Tập test đủ lớn để đánh giá độ tin cậy của mô hình")
    print("   • Hiệu suất dự đoán tốt với mức overfitting chấp nhận được")
elif best_overall_split == '8:2':
    print("   • Split 8:2 có nhiều dữ liệu train hơn, giúp mô hình học tốt hơn")
    print("   • Tập test vẫn đủ lớn để đánh giá")
    print("   • Cân bằng tốt giữa hiệu suất và độ tin cậy")
else:  # 9:1
    print("   • Split 9:1 tối đa hóa dữ liệu train")
    print("   • Có thể có rủi ro về độ tin cậy do tập test nhỏ")
    print("   • Phù hợp khi có ít dữ liệu và cần tối ưu hiệu suất")

print(f"\n   ⚠️  LƯU Ý: Với dữ liệu time series như Bitcoin, nên chọn split cân bằng")
print(f"   để đảm bảo tập test đủ lớn và đại diện cho nhiều giai đoạn thị trường khác nhau.")
================================================================================
PHÂN TÍCH VÀ KHUYẾN NGHỊ
================================================================================

1. PHÂN TÍCH THEO TỪNG TIÊU CHÍ:
   • Tốt nhất theo MAPE: 9:1 (0.02%)
   • Tốt nhất theo RMSE: 7:3 (2,351.02 USD)
   • Tốt nhất theo Val Loss: 8:2 (0.001776)
   • Ít overfitting nhất: 7:3 (gap: 0.000399)

2. ĐIỂM TỔNG HỢP (trọng số: MAPE=30%, RMSE=30%, Val_Loss=25%, Overfitting=15%):
   • 7:3: 1.85 điểm
   • 7:3: 1.85 điểm
   • 8:2: 1.90 điểm
   • 8:2: 1.90 điểm
   • 9:1: 2.25 điểm
   • 9:1: 2.25 điểm

3. KẾT LUẬN VÀ KHUYẾN NGHỊ:
   🏆 MÔ HÌNH TỐT NHẤT: Split 7:3
   📊 Lý do:
      - MAPE: 0.03%
      - RMSE: 2,351.02 USD
      - Best Val Loss: 0.001849
      - Overfitting Gap: 0.000399
      - Tập test có 1,011 mẫu (đủ để đánh giá)
      - Huấn luyện ổn định với 60 epochs

4. NHẬN XÉT CHUNG:
   • Split 7:3 cân bằng tốt giữa kích thước tập train và test
   • Tập test đủ lớn để đánh giá độ tin cậy của mô hình
   • Hiệu suất dự đoán tốt với mức overfitting chấp nhận được

   ⚠️  LƯU Ý: Với dữ liệu time series như Bitcoin, nên chọn split cân bằng
   để đảm bảo tập test đủ lớn và đại diện cho nhiều giai đoạn thị trường khác nhau.
In [36]:
# Vẽ biểu đồ radar cho so sánh tổng thể
import numpy as np
import matplotlib.pyplot as plt

def create_radar_chart():
    # Chuẩn hóa các metrics về scale 0-1 (1 là tốt nhất)
    metrics = ['MAPE', 'RMSE', 'Val_Loss', 'Overfitting', 'Test_Size']
    
    # Lấy giá trị của từng metric (đảo ngược để 1 là tốt nhất)
    data = {}
    for split in splits:
        mape_norm = 1 - (splits_info[split]['mape'] - min([splits_info[s]['mape'] for s in splits])) / \
                   (max([splits_info[s]['mape'] for s in splits]) - min([splits_info[s]['mape'] for s in splits]))
        
        rmse_norm = 1 - (splits_info[split]['rmse'] - min([splits_info[s]['rmse'] for s in splits])) / \
                   (max([splits_info[s]['rmse'] for s in splits]) - min([splits_info[s]['rmse'] for s in splits]))
        
        val_loss_norm = 1 - (splits_info[split]['best_val_loss'] - min([splits_info[s]['best_val_loss'] for s in splits])) / \
                       (max([splits_info[s]['best_val_loss'] for s in splits]) - min([splits_info[s]['best_val_loss'] for s in splits]))
        
        overfitting_gaps = [abs(splits_info[s]['final_val_loss'] - splits_info[s]['final_train_loss']) for s in splits]
        overfitting_norm = 1 - (abs(splits_info[split]['final_val_loss'] - splits_info[split]['final_train_loss']) - min(overfitting_gaps)) / \
                          (max(overfitting_gaps) - min(overfitting_gaps))
        
        test_size_norm = (splits_info[split]['test_size'] - min([splits_info[s]['test_size'] for s in splits])) / \
                        (max([splits_info[s]['test_size'] for s in splits]) - min([splits_info[s]['test_size'] for s in splits]))
        
        data[split] = [mape_norm, rmse_norm, val_loss_norm, overfitting_norm, test_size_norm]
    
    # Tạo radar chart
    angles = np.linspace(0, 2 * np.pi, len(metrics), endpoint=False).tolist()
    angles += angles[:1]  # Complete the circle
    
    fig, ax = plt.subplots(figsize=(10, 10), subplot_kw=dict(projection='polar'))
    
    colors = ['#1f77b4', '#ff7f0e', '#2ca02c']
    
    for i, (split, values) in enumerate(data.items()):
        values += values[:1]  # Complete the circle
        ax.plot(angles, values, 'o-', linewidth=2, label=f'Split {split}', color=colors[i])
        ax.fill(angles, values, alpha=0.25, color=colors[i])
    
    ax.set_xticks(angles[:-1])
    ax.set_xticklabels(metrics)
    ax.set_ylim(0, 1)
    ax.set_title('So sánh tổng thể các Split (1 = Tốt nhất)', size=16, fontweight='bold', pad=20)
    ax.legend(loc='upper right', bbox_to_anchor=(1.2, 1.0))
    ax.grid(True)
    
    plt.tight_layout()
    plt.show()

create_radar_chart()
No description has been provided for this image

ETH¶

Import thư viện¶

In [37]:
import numpy as np
import pandas as pd
import yfinance as yf
import datetime as dt
import matplotlib.pyplot as plt
import math
from keras.models import Sequential
from keras.layers import SimpleRNN, Dense, Dropout
from keras.optimizers import Adam
from keras.callbacks import EarlyStopping, ReduceLROnPlateau
from keras import regularizers
from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, mean_absolute_percentage_error
from typing import Tuple

ETH Dataset¶

Import csv¶

In [38]:
# Đọc file ETH
file_path = "D:\\github_desktop\\Cryptocurrency-Price-Prediction\\Cryptocurrency\\Dataset\\Ethereum Historical Data.csv"

data = pd.read_csv(file_path)

# Loại bỏ dấu phẩy và chuyển đổi thành float cho Price và Open
for col in ['Price', 'Open']:
    data[col] = data[col].str.replace(',', '', regex=False).astype(float)

# Xử lý cột 'Vol.' chứa hậu tố 'K', 'M', 'B' thành số thực
def convert_volume(val):
    val = str(val).replace(',', '').strip()
    if 'K' in val:
        return float(val.replace('K', '')) * 1_000
    elif 'M' in val:
        return float(val.replace('M', '')) * 1_000_000
    elif 'B' in val:
        return float(val.replace('B', '')) * 1_000_000_000
    else:
        try:
            return float(val)
        except ValueError:
            return np.nan  # Trường hợp val là '' hoặc không chuyển được

data['Vol.'] = data['Vol.'].apply(convert_volume)

# Kiểm tra NaN ban đầu trong Vol.
print(f"Trước khi xử lý, số NaN ở Vol.: {data['Vol.'].isna().sum()}")

# Nội suy giá trị Vol. (chỉ sau khi đã convert sang số)
data['Vol.'] = data['Vol.'].interpolate(mdataod='linear')

# Điền 0 cho NaN còn lại
data['Vol.'] = data['Vol.'].fillna(0)

# Kiểm tra NaN sau xử lý
print(f"Sau khi xử lý, số NaN ở Vol.: {data['Vol.'].isna().sum()}")

# Đổi Date sang datetime và đặt làm index
data['Date'] = pd.to_datetime(data['Date'])
data.set_index('Date', inplace=True)
data.sort_index(inplace=True)

# Thông tin dữ liệu
print("Data shape:", data.shape)
print("Columns:", data.columns.tolist())
print("\nFirst 5 rows:")
print(data[['Price', 'Open', 'Vol.']].head())

print(f"Tổng số dữ liệu: {len(data)} dòng")
Trước khi xử lý, số NaN ở Vol.: 8
Sau khi xử lý, số NaN ở Vol.: 0
Data shape: (3370, 6)
Columns: ['Price', 'Open', 'High', 'Low', 'Vol.', 'Change %']

First 5 rows:
            Price   Open     Vol.
Date                             
2016-03-10  11.75  11.20      0.0
2016-03-11  11.95  11.75    180.0
2016-03-12  12.92  11.95    830.0
2016-03-13  15.07  12.92   1300.0
2016-03-14  12.50  15.07  92180.0
Tổng số dữ liệu: 3370 dòng

Chia 7:3¶

Chuẩn hóa dữ liệu¶

In [39]:
# Chuẩn hóa dữ liệu
# Lấy 3 cột Price, Open, Vol. để làm đầu vào và Price làm đầu ra
input_features = data[['Price', 'Open', 'Vol.']].values
target_feature = data[['Price']].values

# Áp dụng MinMaxScaler cho input features
scaler_input = MinMaxScaler(feature_range=(0, 1))
scaled_input = scaler_input.fit_transform(input_features)

# Áp dụng MinMaxScaler cho target feature
scaler_target = MinMaxScaler(feature_range=(0, 1))
scaled_target = scaler_target.fit_transform(target_feature)
In [40]:
# Chia dữ liệu train/test theo tỷ lệ 7:3
train_size = int(len(data) * 0.7)
train_data = data.iloc[0:train_size,:]
test_data = data.iloc[train_size:len(data),:]

# Chia dữ liệu đã chuẩn hóa
scaled_train_input = scaled_input[0:train_size,:]
scaled_test_input = scaled_input[train_size:,:]
scaled_train_target = scaled_target[0:train_size,:]
scaled_test_target = scaled_target[train_size:,:]

print(f"Kích thước tập train: {len(train_data)}")
print(f"Kích thước tập test: {len(test_data)}")
Kích thước tập train: 2359
Kích thước tập test: 1011

Xây dựng mô hình RNN¶

In [41]:
def build_rnn_model_with_regularization(time_step: int, num_features: int) -> Sequential:
    """
    Xây dựng mô hình RNN với regularization

    Args:
        time_step: Số time steps để nhìn về quá khứ
        num_features: Số features đầu vào (Price, Open, Vol = 3)

    Returns:
        Sequential model
    """
    model = Sequential()

    # SimpleRNN layer với regularization
    model.add(SimpleRNN(
        units=50,                       # Số neurons
        input_shape=(time_step, num_features),  # (50, 3)
        kernel_regularizer=regularizers.l2(0.001),    # L2 regularization
        return_sequences=False           # Chỉ cần output cuối cùng
    ))

    # Dropout để tránh overfitting
    model.add(Dropout(0.3))

    # Dense layer ẩn
    model.add(Dense(32, activation='relu'))
    model.add(Dropout(0.2))

    # Output layer: dự đoán 1 giá trị (Price)
    model.add(Dense(1))

    # Optimizer với learning rate nhỏ
    optimizer = Adam(learning_rate=1e-4)
    model.compile(optimizer=optimizer, loss='mean_squared_error', metrics=['mae'])

    return model
In [42]:
# Hàm tạo dữ liệu time series với multiple features
def create_multivariate_time_series_data(input_data: np.ndarray, target_data: np.ndarray, time_step: int) -> Tuple[np.ndarray, np.ndarray]:
    X_data, y_data = [], []
    for i in range(len(input_data) - time_step):
        X_data.append(input_data[i:(i + time_step), :])  # Lấy tất cả features
        y_data.append(target_data[i + time_step, 0])     # Chỉ lấy Price
    return np.array(X_data), np.array(y_data)
In [43]:
# Hàm dự đoán với multiple features
def forecast_multivariate_prices(model: Sequential, input_data: np.ndarray, time_step: int,
                                forecast_days: int, scaler_target: MinMaxScaler) -> np.ndarray:
    temp_input = input_data[-time_step:].reshape(1, time_step, input_data.shape[1])
    lst_output = []

    for _ in range(forecast_days):
        predicted_price = model.predict(temp_input, verbose=0)
        lst_output.append(predicted_price[0].tolist())

        # Tạo input mới cho prediction tiếp theo
        # Giả sử các features khác không đổi, chỉ cập nhật Price
        new_row = temp_input[0, -1, :].copy()
        new_row[0] = predicted_price[0, 0]  # Cập nhật Price prediction

        temp_input = np.append(temp_input[:, 1:, :],
                              new_row.reshape(1, 1, input_data.shape[1]), axis=1)

    # Chuyển đổi lst_output thành numpy array và inverse transform
    lst_output = np.array(lst_output).reshape(-1, 1)
    return scaler_target.inverse_transform(lst_output)

Huấn luyện mô hình¶

In [44]:
# Tạo dữ liệu train và test với time_step = 50
time_step = 50
X_train, y_train = create_multivariate_time_series_data(scaled_train_input, scaled_train_target, time_step)
X_test, y_test = create_multivariate_time_series_data(scaled_test_input, scaled_test_target, time_step)

print(f"X_train shape: {X_train.shape}")
print(f"y_train shape: {y_train.shape}")
print(f"X_test shape: {X_test.shape}")
print(f"y_test shape: {y_test.shape}")

# Xây dựng mô hình RNN
model_rnn = build_rnn_model_with_regularization(time_step, 3)  # 3 features: Price, Open, Vol

# Callbacks để tối ưu hóa
early_stop = EarlyStopping(monitor='val_loss', patience=15, restore_best_weights=True, verbose=1)
reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=10, min_lr=1e-7, verbose=1)

# Huấn luyện mô hình
history_rnn = model_rnn.fit(
    X_train, y_train,
    epochs=60,
    batch_size=16,
    validation_data=(X_test, y_test),
    callbacks=[early_stop, reduce_lr],
    verbose=1
)
X_train shape: (2309, 50, 3)
y_train shape: (2309,)
X_test shape: (961, 50, 3)
y_test shape: (961,)
Epoch 1/60
c:\Users\Hii\AppData\Local\Programs\Python\Python311\Lib\site-packages\keras\src\layers\rnn\rnn.py:199: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead.
  super().__init__(**kwargs)
145/145 ━━━━━━━━━━━━━━━━━━━━ 3s 8ms/step - loss: 0.0858 - mae: 0.1458 - val_loss: 0.0196 - val_mae: 0.0830 - learning_rate: 1.0000e-04
Epoch 2/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0251 - mae: 0.0710 - val_loss: 0.0117 - val_mae: 0.0588 - learning_rate: 1.0000e-04
Epoch 3/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0183 - mae: 0.0615 - val_loss: 0.0110 - val_mae: 0.0625 - learning_rate: 1.0000e-04
Epoch 4/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0166 - mae: 0.0527 - val_loss: 0.0093 - val_mae: 0.0445 - learning_rate: 1.0000e-04
Epoch 5/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0139 - mae: 0.0496 - val_loss: 0.0092 - val_mae: 0.0456 - learning_rate: 1.0000e-04
Epoch 6/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0132 - mae: 0.0461 - val_loss: 0.0075 - val_mae: 0.0347 - learning_rate: 1.0000e-04
Epoch 7/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0127 - mae: 0.0462 - val_loss: 0.0089 - val_mae: 0.0487 - learning_rate: 1.0000e-04
Epoch 8/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0129 - mae: 0.0463 - val_loss: 0.0069 - val_mae: 0.0301 - learning_rate: 1.0000e-04
Epoch 9/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0111 - mae: 0.0405 - val_loss: 0.0085 - val_mae: 0.0475 - learning_rate: 1.0000e-04
Epoch 10/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0122 - mae: 0.0443 - val_loss: 0.0085 - val_mae: 0.0500 - learning_rate: 1.0000e-04
Epoch 11/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0110 - mae: 0.0407 - val_loss: 0.0062 - val_mae: 0.0248 - learning_rate: 1.0000e-04
Epoch 12/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0100 - mae: 0.0387 - val_loss: 0.0083 - val_mae: 0.0495 - learning_rate: 1.0000e-04
Epoch 13/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0092 - mae: 0.0361 - val_loss: 0.0079 - val_mae: 0.0466 - learning_rate: 1.0000e-04
Epoch 14/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0090 - mae: 0.0340 - val_loss: 0.0084 - val_mae: 0.0513 - learning_rate: 1.0000e-04
Epoch 15/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0094 - mae: 0.0373 - val_loss: 0.0067 - val_mae: 0.0348 - learning_rate: 1.0000e-04
Epoch 16/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0089 - mae: 0.0357 - val_loss: 0.0058 - val_mae: 0.0243 - learning_rate: 1.0000e-04
Epoch 17/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0097 - mae: 0.0379 - val_loss: 0.0079 - val_mae: 0.0481 - learning_rate: 1.0000e-04
Epoch 18/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0085 - mae: 0.0349 - val_loss: 0.0095 - val_mae: 0.0635 - learning_rate: 1.0000e-04
Epoch 19/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0083 - mae: 0.0335 - val_loss: 0.0062 - val_mae: 0.0326 - learning_rate: 1.0000e-04
Epoch 20/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0085 - mae: 0.0340 - val_loss: 0.0076 - val_mae: 0.0502 - learning_rate: 1.0000e-04
Epoch 21/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0080 - mae: 0.0333 - val_loss: 0.0077 - val_mae: 0.0509 - learning_rate: 1.0000e-04
Epoch 22/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0082 - mae: 0.0334 - val_loss: 0.0065 - val_mae: 0.0386 - learning_rate: 1.0000e-04
Epoch 23/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0082 - mae: 0.0344 - val_loss: 0.0072 - val_mae: 0.0473 - learning_rate: 1.0000e-04
Epoch 24/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0077 - mae: 0.0328 - val_loss: 0.0063 - val_mae: 0.0377 - learning_rate: 1.0000e-04
Epoch 25/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0077 - mae: 0.0324 - val_loss: 0.0061 - val_mae: 0.0359 - learning_rate: 1.0000e-04
Epoch 26/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0074 - mae: 0.0314 - val_loss: 0.0055 - val_mae: 0.0290 - learning_rate: 1.0000e-04
Epoch 27/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0074 - mae: 0.0312 - val_loss: 0.0061 - val_mae: 0.0374 - learning_rate: 1.0000e-04
Epoch 28/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0069 - mae: 0.0299 - val_loss: 0.0054 - val_mae: 0.0292 - learning_rate: 1.0000e-04
Epoch 29/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0068 - mae: 0.0297 - val_loss: 0.0071 - val_mae: 0.0486 - learning_rate: 1.0000e-04
Epoch 30/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0070 - mae: 0.0301 - val_loss: 0.0071 - val_mae: 0.0486 - learning_rate: 1.0000e-04
Epoch 31/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0067 - mae: 0.0305 - val_loss: 0.0073 - val_mae: 0.0507 - learning_rate: 1.0000e-04
Epoch 32/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0071 - mae: 0.0307 - val_loss: 0.0061 - val_mae: 0.0403 - learning_rate: 1.0000e-04
Epoch 33/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0068 - mae: 0.0311 - val_loss: 0.0066 - val_mae: 0.0459 - learning_rate: 1.0000e-04
Epoch 34/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0066 - mae: 0.0299 - val_loss: 0.0056 - val_mae: 0.0349 - learning_rate: 1.0000e-04
Epoch 35/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0067 - mae: 0.0301 - val_loss: 0.0046 - val_mae: 0.0226 - learning_rate: 1.0000e-04
Epoch 36/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0064 - mae: 0.0290 - val_loss: 0.0071 - val_mae: 0.0524 - learning_rate: 1.0000e-04
Epoch 37/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0062 - mae: 0.0291 - val_loss: 0.0052 - val_mae: 0.0334 - learning_rate: 1.0000e-04
Epoch 38/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0064 - mae: 0.0287 - val_loss: 0.0052 - val_mae: 0.0339 - learning_rate: 1.0000e-04
Epoch 39/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0061 - mae: 0.0286 - val_loss: 0.0057 - val_mae: 0.0384 - learning_rate: 1.0000e-04
Epoch 40/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0066 - mae: 0.0300 - val_loss: 0.0048 - val_mae: 0.0287 - learning_rate: 1.0000e-04
Epoch 41/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0062 - mae: 0.0295 - val_loss: 0.0049 - val_mae: 0.0327 - learning_rate: 1.0000e-04
Epoch 42/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0059 - mae: 0.0286 - val_loss: 0.0046 - val_mae: 0.0279 - learning_rate: 1.0000e-04
Epoch 43/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0056 - mae: 0.0281 - val_loss: 0.0047 - val_mae: 0.0296 - learning_rate: 1.0000e-04
Epoch 44/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0060 - mae: 0.0302 - val_loss: 0.0062 - val_mae: 0.0473 - learning_rate: 1.0000e-04
Epoch 45/60
135/145 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.0053 - mae: 0.0268
Epoch 45: ReduceLROnPlateau reducing learning rate to 4.999999873689376e-05.
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0054 - mae: 0.0269 - val_loss: 0.0051 - val_mae: 0.0363 - learning_rate: 1.0000e-04
Epoch 46/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0060 - mae: 0.0298 - val_loss: 0.0053 - val_mae: 0.0388 - learning_rate: 5.0000e-05
Epoch 47/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0057 - mae: 0.0279 - val_loss: 0.0049 - val_mae: 0.0343 - learning_rate: 5.0000e-05
Epoch 48/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0062 - mae: 0.0308 - val_loss: 0.0047 - val_mae: 0.0314 - learning_rate: 5.0000e-05
Epoch 49/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0056 - mae: 0.0286 - val_loss: 0.0050 - val_mae: 0.0362 - learning_rate: 5.0000e-05
Epoch 50/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0056 - mae: 0.0288 - val_loss: 0.0047 - val_mae: 0.0327 - learning_rate: 5.0000e-05
Epoch 51/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0056 - mae: 0.0281 - val_loss: 0.0043 - val_mae: 0.0273 - learning_rate: 5.0000e-05
Epoch 52/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0053 - mae: 0.0275 - val_loss: 0.0050 - val_mae: 0.0369 - learning_rate: 5.0000e-05
Epoch 53/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0056 - mae: 0.0280 - val_loss: 0.0045 - val_mae: 0.0308 - learning_rate: 5.0000e-05
Epoch 54/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0062 - mae: 0.0306 - val_loss: 0.0042 - val_mae: 0.0269 - learning_rate: 5.0000e-05
Epoch 55/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0058 - mae: 0.0288 - val_loss: 0.0044 - val_mae: 0.0301 - learning_rate: 5.0000e-05
Epoch 56/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0054 - mae: 0.0282 - val_loss: 0.0040 - val_mae: 0.0243 - learning_rate: 5.0000e-05
Epoch 57/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0057 - mae: 0.0292 - val_loss: 0.0038 - val_mae: 0.0211 - learning_rate: 5.0000e-05
Epoch 58/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0056 - mae: 0.0293 - val_loss: 0.0036 - val_mae: 0.0181 - learning_rate: 5.0000e-05
Epoch 59/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0053 - mae: 0.0278 - val_loss: 0.0038 - val_mae: 0.0235 - learning_rate: 5.0000e-05
Epoch 60/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0050 - mae: 0.0271 - val_loss: 0.0037 - val_mae: 0.0216 - learning_rate: 5.0000e-05
Restoring model weights from the end of the best epoch: 58.

Đánh giá mô hình¶

In [45]:
# Vẽ val_loss để đánh giá overfitting
plt.figure(figsize=(12, 8))

plt.subplot(2, 1, 1)
plt.plot(history_rnn.history['loss'], label='Training Loss', linewidth=2)
plt.plot(history_rnn.history['val_loss'], label='Validation Loss', linewidth=2)
plt.title('RNN Model Loss - Training vs Validation')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# Tìm epoch có val_loss thấp nhất
best_epoch = np.argmin(history_rnn.history['val_loss']) + 1
best_val_loss = min(history_rnn.history['val_loss'])
print(f"Epoch tốt nhất: {best_epoch} với val_loss: {best_val_loss:.6f}")
No description has been provided for this image
Epoch tốt nhất: 58 với val_loss: 0.003579

Dự đoán và trực quan hóa¶

In [46]:
# Dự đoán 30, 60, 90 ngày tiếp theo
last_data_scaled = scaled_test_input[-time_step:]

forecasted_prices_30 = forecast_multivariate_prices(model_rnn, last_data_scaled, time_step, 30, scaler_target)
forecasted_prices_60 = forecast_multivariate_prices(model_rnn, last_data_scaled, time_step, 60, scaler_target)
forecasted_prices_90 = forecast_multivariate_prices(model_rnn, last_data_scaled, time_step, 90, scaler_target)

# Dự đoán giá trên tập kiểm tra
test_predict_scaled = model_rnn.predict(X_test)
test_predict_rnn = scaler_target.inverse_transform(test_predict_scaled)

# Tạo DataFrame cho các dự đoán
forecast_dates_30 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=30, freq='D')
forecast_dates_60 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=60, freq='D')
forecast_dates_90 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=90, freq='D')

forecast_df_30 = pd.DataFrame(forecasted_prices_30, index=forecast_dates_30, columns=['Price'])
forecast_df_60 = pd.DataFrame(forecasted_prices_60, index=forecast_dates_60, columns=['Price'])
forecast_df_90 = pd.DataFrame(forecasted_prices_90, index=forecast_dates_90, columns=['Price'])

# Trực quan hóa kết quả
plt.figure(figsize=(16, 10))

# Vẽ giá thực tế
plt.plot(data.index, data['Price'], label='Giá thực tế', color='blue', linewidth=2, alpha=0.8)

# Vẽ dự đoán trên tập test
plt.plot(test_data.index[time_step:], test_predict_rnn,
         label='Dự đoán trên tập test', color='orange', linewidth=2, alpha=0.8)

# Vẽ các dự đoán tương lai
plt.plot(forecast_df_30.index, forecast_df_30['Price'],
         label='Dự đoán 30 ngày tiếp theo', color='red', linestyle='--', linewidth=2, alpha=0.7)

plt.plot(forecast_df_60.index, forecast_df_60['Price'],
         label='Dự đoán 60 ngày tiếp theo', color='green', linestyle='--', linewidth=2, alpha=0.5)

plt.plot(forecast_df_90.index, forecast_df_90['Price'],
         label='Dự đoán 90 ngày tiếp theo', color='purple', linestyle='--', linewidth=2, alpha=0.3)

# Thêm đường thẳng đứng để phân biệt vùng dữ liệu
plt.axvline(x=data.index[-1], color='black', linestyle=':', alpha=0.7,
            label='Ngày cuối cùng của dữ liệu thực tế')

plt.title(f'Dự đoán giá ETH bằng RNN (Time Step = {time_step})\nDự đoán 30, 60, 90 ngày tiếp theo',
          fontsize=16, fontweight='bold')
plt.xlabel('Ngày', fontsize=12)
plt.ylabel('Giá (USD)', fontsize=12)
plt.legend(fontsize=10, loc='upper left')
plt.grid(True, alpha=0.3)

plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
31/31 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step
No description has been provided for this image
In [47]:
# Đánh giá mô hình
# Lấy giá trị thực tế trên tập test
y_test_actual = test_data['Price'].values[time_step:]

# Tính toán các metrics
mape = mean_absolute_percentage_error(y_test_actual, test_predict_rnn.flatten())
mse = mean_squared_error(y_test_actual, test_predict_rnn.flatten())
rmse = np.sqrt(mse)

print(f'Kết quả đánh giá mô hình RNN (Time Step = {time_step}):')
print(f'MAPE: {mape:.2f}%')
print(f'MSE: {mse:.2f}')
print(f'RMSE: {rmse:.2f}')
print(f'Số epochs huấn luyện: {len(history_rnn.history["loss"])}')

# Hiển thị thông tin dự đoán 30 ngày
print(f'\nDự đoán giá ETH 30 ngày tiếp theo:')
print(f'Giá cao nhất: ${forecasted_prices_30.max():.2f}')
print(f'Giá thấp nhất: ${forecasted_prices_30.min():.2f}')
print(f'Giá trung bình: ${forecasted_prices_30.mean():.2f}')
Kết quả đánh giá mô hình RNN (Time Step = 50):
MAPE: 0.03%
MSE: 15641.58
RMSE: 125.07
Số epochs huấn luyện: 60

Dự đoán giá ETH 30 ngày tiếp theo:
Giá cao nhất: $2527.08
Giá thấp nhất: $2330.73
Giá trung bình: $2425.73

Chia 8:2¶

Chuẩn hóa dữ liệu 8:2¶

In [48]:
# Chuẩn hóa dữ liệu
# Lấy 3 cột Price, Open, Vol. để làm đầu vào và Price làm đầu ra
input_features = data[['Price', 'Open', 'Vol.']].values
target_feature = data[['Price']].values

# Áp dụng MinMaxScaler cho input features
scaler_input = MinMaxScaler(feature_range=(0, 1))
scaled_input = scaler_input.fit_transform(input_features)

# Áp dụng MinMaxScaler cho target feature
scaler_target = MinMaxScaler(feature_range=(0, 1))
scaled_target = scaler_target.fit_transform(target_feature)
In [49]:
# Chia dữ liệu train/test theo tỷ lệ 8:2
train_size_82 = int(len(data) * 0.8)
train_data_82 = data.iloc[0:train_size_82,:]
test_data_82 = data.iloc[train_size_82:len(data),:]

# Chia dữ liệu đã chuẩn hóa
scaled_train_input_82 = scaled_input[0:train_size_82,:]
scaled_test_input_82 = scaled_input[train_size_82:,:]
scaled_train_target_82 = scaled_target[0:train_size_82,:]
scaled_test_target_82 = scaled_target[train_size_82:,:]

print(f"Kích thước tập train 8:2: {len(train_data_82)}")
print(f"Kích thước tập test 8:2: {len(test_data_82)}")
Kích thước tập train 8:2: 2696
Kích thước tập test 8:2: 674

Xây dựng mô hình RNN¶

In [50]:
def build_rnn_model_with_regularization(time_step: int, num_features: int) -> Sequential:
    """
    Xây dựng mô hình RNN với regularization

    Args:
        time_step: Số time steps để nhìn về quá khứ
        num_features: Số features đầu vào (Price, Open, Vol = 3)

    Returns:
        Sequential model
    """
    model = Sequential()

    # SimpleRNN layer với regularization
    model.add(SimpleRNN(
        units=50,                       # Số neurons
        input_shape=(time_step, num_features),  # (50, 3)
        kernel_regularizer=regularizers.l2(0.001),    # L2 regularization
        return_sequences=False           # Chỉ cần output cuối cùng
    ))

    # Dropout để tránh overfitting
    model.add(Dropout(0.3))

    # Dense layer ẩn
    model.add(Dense(32, activation='relu'))
    model.add(Dropout(0.2))

    # Output layer: dự đoán 1 giá trị (Price)
    model.add(Dense(1))

    # Optimizer với learning rate nhỏ
    optimizer = Adam(learning_rate=1e-4)
    model.compile(optimizer=optimizer, loss='mean_squared_error', metrics=['mae'])

    return model
In [51]:
# Hàm tạo dữ liệu time series với multiple features
def create_multivariate_time_series_data(input_data: np.ndarray, target_data: np.ndarray, time_step: int) -> Tuple[np.ndarray, np.ndarray]:
    X_data, y_data = [], []
    for i in range(len(input_data) - time_step):
        X_data.append(input_data[i:(i + time_step), :])  # Lấy tất cả features
        y_data.append(target_data[i + time_step, 0])     # Chỉ lấy Price
    return np.array(X_data), np.array(y_data)
In [52]:
# Hàm dự đoán với multiple features
def forecast_multivariate_prices(model: Sequential, input_data: np.ndarray, time_step: int,
                                forecast_days: int, scaler_target: MinMaxScaler) -> np.ndarray:
    temp_input = input_data[-time_step:].reshape(1, time_step, input_data.shape[1])
    lst_output = []

    for _ in range(forecast_days):
        predicted_price = model.predict(temp_input, verbose=0)
        lst_output.append(predicted_price[0].tolist())

        # Tạo input mới cho prediction tiếp theo
        # Giả sử các features khác không đổi, chỉ cập nhật Price
        new_row = temp_input[0, -1, :].copy()
        new_row[0] = predicted_price[0, 0]  # Cập nhật Price prediction

        temp_input = np.append(temp_input[:, 1:, :],
                              new_row.reshape(1, 1, input_data.shape[1]), axis=1)

    # Chuyển đổi lst_output thành numpy array và inverse transform
    lst_output = np.array(lst_output).reshape(-1, 1)
    return scaler_target.inverse_transform(lst_output)

Huấn luyện mô hình 8:2¶

In [53]:
# Tạo dữ liệu train và test với time_step = 50 cho split 8:2
X_train_82, y_train_82 = create_multivariate_time_series_data(scaled_train_input_82, scaled_train_target_82, time_step)
X_test_82, y_test_82 = create_multivariate_time_series_data(scaled_test_input_82, scaled_test_target_82, time_step)

print(f"X_train_82 shape: {X_train_82.shape}")
print(f"y_train_82 shape: {y_train_82.shape}")
print(f"X_test_82 shape: {X_test_82.shape}")
print(f"y_test_82 shape: {y_test_82.shape}")

# Xây dựng mô hình RNN cho split 8:2
model_rnn_82 = build_rnn_model_with_regularization(time_step, 3)  # 3 features: Price, Open, Vol

# Callbacks để tối ưu hóa
early_stop_82 = EarlyStopping(monitor='val_loss', patience=15, restore_best_weights=True, verbose=1)
reduce_lr_82 = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=10, min_lr=1e-7, verbose=1)

# Huấn luyện mô hình 8:2
history_rnn_82 = model_rnn_82.fit(
    X_train_82, y_train_82,
    epochs=60,
    batch_size=16,
    validation_data=(X_test_82, y_test_82),
    callbacks=[early_stop_82, reduce_lr_82],
    verbose=1
)
X_train_82 shape: (2646, 50, 3)
y_train_82 shape: (2646,)
X_test_82 shape: (624, 50, 3)
y_test_82 shape: (624,)
Epoch 1/60
c:\Users\Hii\AppData\Local\Programs\Python\Python311\Lib\site-packages\keras\src\layers\rnn\rnn.py:199: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead.
  super().__init__(**kwargs)
166/166 ━━━━━━━━━━━━━━━━━━━━ 4s 10ms/step - loss: 0.0906 - mae: 0.1641 - val_loss: 0.0163 - val_mae: 0.0852 - learning_rate: 1.0000e-04
Epoch 2/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 3s 12ms/step - loss: 0.0234 - mae: 0.0757 - val_loss: 0.0125 - val_mae: 0.0727 - learning_rate: 1.0000e-04
Epoch 3/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 9ms/step - loss: 0.0172 - mae: 0.0607 - val_loss: 0.0142 - val_mae: 0.0856 - learning_rate: 1.0000e-04
Epoch 4/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0148 - mae: 0.0529 - val_loss: 0.0144 - val_mae: 0.0859 - learning_rate: 1.0000e-04
Epoch 5/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0134 - mae: 0.0511 - val_loss: 0.0166 - val_mae: 0.0983 - learning_rate: 1.0000e-04
Epoch 6/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0127 - mae: 0.0483 - val_loss: 0.0124 - val_mae: 0.0761 - learning_rate: 1.0000e-04
Epoch 7/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0120 - mae: 0.0456 - val_loss: 0.0118 - val_mae: 0.0728 - learning_rate: 1.0000e-04
Epoch 8/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0131 - mae: 0.0478 - val_loss: 0.0143 - val_mae: 0.0882 - learning_rate: 1.0000e-04
Epoch 9/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0113 - mae: 0.0420 - val_loss: 0.0110 - val_mae: 0.0686 - learning_rate: 1.0000e-04
Epoch 10/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0111 - mae: 0.0432 - val_loss: 0.0122 - val_mae: 0.0761 - learning_rate: 1.0000e-04
Epoch 11/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0103 - mae: 0.0398 - val_loss: 0.0091 - val_mae: 0.0555 - learning_rate: 1.0000e-04
Epoch 12/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0100 - mae: 0.0402 - val_loss: 0.0088 - val_mae: 0.0534 - learning_rate: 1.0000e-04
Epoch 13/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0093 - mae: 0.0381 - val_loss: 0.0102 - val_mae: 0.0652 - learning_rate: 1.0000e-04
Epoch 14/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0092 - mae: 0.0376 - val_loss: 0.0109 - val_mae: 0.0708 - learning_rate: 1.0000e-04
Epoch 15/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0091 - mae: 0.0365 - val_loss: 0.0098 - val_mae: 0.0622 - learning_rate: 1.0000e-04
Epoch 16/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0085 - mae: 0.0346 - val_loss: 0.0085 - val_mae: 0.0534 - learning_rate: 1.0000e-04
Epoch 17/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0089 - mae: 0.0358 - val_loss: 0.0078 - val_mae: 0.0486 - learning_rate: 1.0000e-04
Epoch 18/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0079 - mae: 0.0332 - val_loss: 0.0103 - val_mae: 0.0684 - learning_rate: 1.0000e-04
Epoch 19/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0080 - mae: 0.0343 - val_loss: 0.0090 - val_mae: 0.0600 - learning_rate: 1.0000e-04
Epoch 20/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0082 - mae: 0.0351 - val_loss: 0.0070 - val_mae: 0.0432 - learning_rate: 1.0000e-04
Epoch 21/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0078 - mae: 0.0327 - val_loss: 0.0081 - val_mae: 0.0547 - learning_rate: 1.0000e-04
Epoch 22/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0076 - mae: 0.0333 - val_loss: 0.0064 - val_mae: 0.0394 - learning_rate: 1.0000e-04
Epoch 23/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0074 - mae: 0.0324 - val_loss: 0.0085 - val_mae: 0.0585 - learning_rate: 1.0000e-04
Epoch 24/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0070 - mae: 0.0318 - val_loss: 0.0065 - val_mae: 0.0409 - learning_rate: 1.0000e-04
Epoch 25/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0079 - mae: 0.0351 - val_loss: 0.0067 - val_mae: 0.0450 - learning_rate: 1.0000e-04
Epoch 26/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0067 - mae: 0.0313 - val_loss: 0.0062 - val_mae: 0.0395 - learning_rate: 1.0000e-04
Epoch 27/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0073 - mae: 0.0329 - val_loss: 0.0065 - val_mae: 0.0448 - learning_rate: 1.0000e-04
Epoch 28/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0071 - mae: 0.0325 - val_loss: 0.0055 - val_mae: 0.0344 - learning_rate: 1.0000e-04
Epoch 29/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0065 - mae: 0.0307 - val_loss: 0.0053 - val_mae: 0.0324 - learning_rate: 1.0000e-04
Epoch 30/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0067 - mae: 0.0322 - val_loss: 0.0056 - val_mae: 0.0371 - learning_rate: 1.0000e-04
Epoch 31/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0064 - mae: 0.0313 - val_loss: 0.0063 - val_mae: 0.0447 - learning_rate: 1.0000e-04
Epoch 32/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0060 - mae: 0.0296 - val_loss: 0.0053 - val_mae: 0.0356 - learning_rate: 1.0000e-04
Epoch 33/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0062 - mae: 0.0304 - val_loss: 0.0056 - val_mae: 0.0381 - learning_rate: 1.0000e-04
Epoch 34/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0061 - mae: 0.0310 - val_loss: 0.0051 - val_mae: 0.0338 - learning_rate: 1.0000e-04
Epoch 35/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0063 - mae: 0.0318 - val_loss: 0.0075 - val_mae: 0.0593 - learning_rate: 1.0000e-04
Epoch 36/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0061 - mae: 0.0314 - val_loss: 0.0051 - val_mae: 0.0371 - learning_rate: 1.0000e-04
Epoch 37/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0057 - mae: 0.0298 - val_loss: 0.0044 - val_mae: 0.0281 - learning_rate: 1.0000e-04
Epoch 38/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0058 - mae: 0.0305 - val_loss: 0.0055 - val_mae: 0.0431 - learning_rate: 1.0000e-04
Epoch 39/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0053 - mae: 0.0285 - val_loss: 0.0048 - val_mae: 0.0356 - learning_rate: 1.0000e-04
Epoch 40/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0052 - mae: 0.0287 - val_loss: 0.0055 - val_mae: 0.0439 - learning_rate: 1.0000e-04
Epoch 41/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0053 - mae: 0.0295 - val_loss: 0.0048 - val_mae: 0.0371 - learning_rate: 1.0000e-04
Epoch 42/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0048 - mae: 0.0275 - val_loss: 0.0037 - val_mae: 0.0216 - learning_rate: 1.0000e-04
Epoch 43/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0052 - mae: 0.0295 - val_loss: 0.0037 - val_mae: 0.0227 - learning_rate: 1.0000e-04
Epoch 44/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0049 - mae: 0.0283 - val_loss: 0.0054 - val_mae: 0.0450 - learning_rate: 1.0000e-04
Epoch 45/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0047 - mae: 0.0271 - val_loss: 0.0042 - val_mae: 0.0309 - learning_rate: 1.0000e-04
Epoch 46/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0049 - mae: 0.0288 - val_loss: 0.0035 - val_mae: 0.0225 - learning_rate: 1.0000e-04
Epoch 47/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0047 - mae: 0.0288 - val_loss: 0.0037 - val_mae: 0.0265 - learning_rate: 1.0000e-04
Epoch 48/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0047 - mae: 0.0279 - val_loss: 0.0039 - val_mae: 0.0303 - learning_rate: 1.0000e-04
Epoch 49/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0045 - mae: 0.0271 - val_loss: 0.0044 - val_mae: 0.0373 - learning_rate: 1.0000e-04
Epoch 50/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0046 - mae: 0.0287 - val_loss: 0.0040 - val_mae: 0.0330 - learning_rate: 1.0000e-04
Epoch 51/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0048 - mae: 0.0294 - val_loss: 0.0032 - val_mae: 0.0207 - learning_rate: 1.0000e-04
Epoch 52/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0042 - mae: 0.0263 - val_loss: 0.0042 - val_mae: 0.0372 - learning_rate: 1.0000e-04
Epoch 53/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0045 - mae: 0.0293 - val_loss: 0.0038 - val_mae: 0.0317 - learning_rate: 1.0000e-04
Epoch 54/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0042 - mae: 0.0275 - val_loss: 0.0045 - val_mae: 0.0411 - learning_rate: 1.0000e-04
Epoch 55/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0043 - mae: 0.0285 - val_loss: 0.0041 - val_mae: 0.0371 - learning_rate: 1.0000e-04
Epoch 56/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0040 - mae: 0.0272 - val_loss: 0.0038 - val_mae: 0.0336 - learning_rate: 1.0000e-04
Epoch 57/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0041 - mae: 0.0276 - val_loss: 0.0031 - val_mae: 0.0252 - learning_rate: 1.0000e-04
Epoch 58/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0043 - mae: 0.0283 - val_loss: 0.0035 - val_mae: 0.0310 - learning_rate: 1.0000e-04
Epoch 59/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0039 - mae: 0.0265 - val_loss: 0.0026 - val_mae: 0.0175 - learning_rate: 1.0000e-04
Epoch 60/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0039 - mae: 0.0264 - val_loss: 0.0026 - val_mae: 0.0182 - learning_rate: 1.0000e-04
Restoring model weights from the end of the best epoch: 60.

Đánh giá mô hình 8:2¶

In [54]:
# Vẽ val_loss để đánh giá overfitting cho split 8:2
plt.figure(figsize=(12, 8))

plt.subplot(2, 1, 1)
plt.plot(history_rnn_82.history['loss'], label='Training Loss', linewidth=2)
plt.plot(history_rnn_82.history['val_loss'], label='Validation Loss', linewidth=2)
plt.title('RNN Model Loss 8:2 Split - Training vs Validation')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# Tìm epoch có val_loss thấp nhất cho split 8:2
best_epoch_82 = np.argmin(history_rnn_82.history['val_loss']) + 1
best_val_loss_82 = min(history_rnn_82.history['val_loss'])
print(f"Epoch tốt nhất (8:2): {best_epoch_82} với val_loss: {best_val_loss_82:.6f}")
No description has been provided for this image
Epoch tốt nhất (8:2): 60 với val_loss: 0.002610

Dự đoán và trực quan hóa 8:2¶

In [55]:
# Dự đoán 30, 60, 90 ngày tiếp theo cho split 8:2
last_data_scaled_82 = scaled_test_input_82[-time_step:]

forecasted_prices_30_82 = forecast_multivariate_prices(model_rnn_82, last_data_scaled_82, time_step, 30, scaler_target)
forecasted_prices_60_82 = forecast_multivariate_prices(model_rnn_82, last_data_scaled_82, time_step, 60, scaler_target)
forecasted_prices_90_82 = forecast_multivariate_prices(model_rnn_82, last_data_scaled_82, time_step, 90, scaler_target)

# Dự đoán giá trên tập kiểm tra cho split 8:2
test_predict_scaled_82 = model_rnn_82.predict(X_test_82)
test_predict_rnn_82 = scaler_target.inverse_transform(test_predict_scaled_82)

# Tạo DataFrame cho các dự đoán 8:2
forecast_dates_30_82 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=30, freq='D')
forecast_dates_60_82 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=60, freq='D')
forecast_dates_90_82 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=90, freq='D')

forecast_df_30_82 = pd.DataFrame(forecasted_prices_30_82, index=forecast_dates_30_82, columns=['Price'])
forecast_df_60_82 = pd.DataFrame(forecasted_prices_60_82, index=forecast_dates_60_82, columns=['Price'])
forecast_df_90_82 = pd.DataFrame(forecasted_prices_90_82, index=forecast_dates_90_82, columns=['Price'])

# Trực quan hóa kết quả cho split 8:2
plt.figure(figsize=(16, 10))

# Vẽ giá thực tế
plt.plot(data.index, data['Price'], label='Giá thực tế', color='blue', linewidth=2, alpha=0.8)

# Vẽ dự đoán trên tập test 8:2
plt.plot(test_data_82.index[time_step:], test_predict_rnn_82,
         label='Dự đoán trên tập test (8:2)', color='orange', linewidth=2, alpha=0.8)

# Vẽ các dự đoán tương lai 8:2
plt.plot(forecast_df_30_82.index, forecast_df_30_82['Price'],
         label='Dự đoán 30 ngày tiếp theo (8:2)', color='red', linestyle='--', linewidth=2, alpha=0.7)

plt.plot(forecast_df_60_82.index, forecast_df_60_82['Price'],
         label='Dự đoán 60 ngày tiếp theo (8:2)', color='green', linestyle='--', linewidth=2, alpha=0.5)

plt.plot(forecast_df_90_82.index, forecast_df_90_82['Price'],
         label='Dự đoán 90 ngày tiếp theo (8:2)', color='purple', linestyle='--', linewidth=2, alpha=0.3)

# Thêm đường thẳng đứng để phân biệt vùng dữ liệu
plt.axvline(x=data.index[-1], color='black', linestyle=':', alpha=0.7,
            label='Ngày cuối cùng của dữ liệu thực tế')

plt.title(f'Dự đoán giá ETH bằng RNN (8:2 Split, Time Step = {time_step})\nDự đoán 30, 60, 90 ngày tiếp theo',
          fontsize=16, fontweight='bold')
plt.xlabel('Ngày', fontsize=12)
plt.ylabel('Giá (USD)', fontsize=12)
plt.legend(fontsize=10, loc='upper left')
plt.grid(True, alpha=0.3)

plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
20/20 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step  
No description has been provided for this image
In [56]:
# Đánh giá mô hình 8:2
# Lấy giá trị thực tế trên tập test 8:2
y_test_actual_82 = test_data_82['Price'].values[time_step:]

# Tính toán các metrics cho split 8:2
mape_82 = mean_absolute_percentage_error(y_test_actual_82, test_predict_rnn_82.flatten())
mse_82 = mean_squared_error(y_test_actual_82, test_predict_rnn_82.flatten())
rmse_82 = np.sqrt(mse_82)

print(f'Kết quả đánh giá mô hình RNN 8:2 Split (Time Step = {time_step}):')
print(f'MAPE: {mape_82:.2f}%')
print(f'MSE: {mse_82:.2f}')
print(f'RMSE: {rmse_82:.2f}')
print(f'Số epochs huấn luyện: {len(history_rnn_82.history["loss"])}')

# Hiển thị thông tin dự đoán 30 ngày cho 8:2
print(f'\nDự đoán giá ETH 30 ngày tiếp theo (8:2):')
print(f'Giá cao nhất: ${forecasted_prices_30_82.max():.2f}')
print(f'Giá thấp nhất: ${forecasted_prices_30_82.min():.2f}')
print(f'Giá trung bình: ${forecasted_prices_30_82.mean():.2f}')
Kết quả đánh giá mô hình RNN 8:2 Split (Time Step = 50):
MAPE: 0.03%
MSE: 14668.50
RMSE: 121.11
Số epochs huấn luyện: 60

Dự đoán giá ETH 30 ngày tiếp theo (8:2):
Giá cao nhất: $2543.50
Giá thấp nhất: $2458.92
Giá trung bình: $2484.52

Chia 9:1¶

Chuẩn hóa dữ liệu 9:1¶

In [57]:
# Chuẩn hóa dữ liệu
# Lấy 3 cột Price, Open, Vol. để làm đầu vào và Price làm đầu ra
input_features = data[['Price', 'Open', 'Vol.']].values
target_feature = data[['Price']].values

# Áp dụng MinMaxScaler cho input features
scaler_input = MinMaxScaler(feature_range=(0, 1))
scaled_input = scaler_input.fit_transform(input_features)

# Áp dụng MinMaxScaler cho target feature
scaler_target = MinMaxScaler(feature_range=(0, 1))
scaled_target = scaler_target.fit_transform(target_feature)
In [58]:
# Chia dữ liệu train/test theo tỷ lệ 9:1
train_size_91 = int(len(data) * 0.9)
train_data_91 = data.iloc[0:train_size_91,:]
test_data_91 = data.iloc[train_size_91:len(data),:]

# Chia dữ liệu đã chuẩn hóa
scaled_train_input_91 = scaled_input[0:train_size_91,:]
scaled_test_input_91 = scaled_input[train_size_91:,:]
scaled_train_target_91 = scaled_target[0:train_size_91,:]
scaled_test_target_91 = scaled_target[train_size_91:,:]

print(f"Kích thước tập train 9:1: {len(train_data_91)}")
print(f"Kích thước tập test 9:1: {len(test_data_91)}")
Kích thước tập train 9:1: 3033
Kích thước tập test 9:1: 337

Xây dựng mô hình RNN¶

In [59]:
def build_rnn_model_with_regularization(time_step: int, num_features: int) -> Sequential:
    """
    Xây dựng mô hình RNN với regularization

    Args:
        time_step: Số time steps để nhìn về quá khứ
        num_features: Số features đầu vào (Price, Open, Vol = 3)

    Returns:
        Sequential model
    """
    model = Sequential()

    # SimpleRNN layer với regularization
    model.add(SimpleRNN(
        units=50,                       # Số neurons
        input_shape=(time_step, num_features),  # (50, 3)
        kernel_regularizer=regularizers.l2(0.001),    # L2 regularization
        return_sequences=False           # Chỉ cần output cuối cùng
    ))

    # Dropout để tránh overfitting
    model.add(Dropout(0.3))

    # Dense layer ẩn
    model.add(Dense(32, activation='relu'))
    model.add(Dropout(0.2))

    # Output layer: dự đoán 1 giá trị (Price)
    model.add(Dense(1))

    # Optimizer với learning rate nhỏ
    optimizer = Adam(learning_rate=1e-4)
    model.compile(optimizer=optimizer, loss='mean_squared_error', metrics=['mae'])

    return model
In [60]:
# Hàm tạo dữ liệu time series với multiple features
def create_multivariate_time_series_data(input_data: np.ndarray, target_data: np.ndarray, time_step: int) -> Tuple[np.ndarray, np.ndarray]:
    X_data, y_data = [], []
    for i in range(len(input_data) - time_step):
        X_data.append(input_data[i:(i + time_step), :])  # Lấy tất cả features
        y_data.append(target_data[i + time_step, 0])     # Chỉ lấy Price
    return np.array(X_data), np.array(y_data)
In [61]:
# Hàm dự đoán với multiple features
def forecast_multivariate_prices(model: Sequential, input_data: np.ndarray, time_step: int,
                                forecast_days: int, scaler_target: MinMaxScaler) -> np.ndarray:
    temp_input = input_data[-time_step:].reshape(1, time_step, input_data.shape[1])
    lst_output = []

    for _ in range(forecast_days):
        predicted_price = model.predict(temp_input, verbose=0)
        lst_output.append(predicted_price[0].tolist())

        # Tạo input mới cho prediction tiếp theo
        # Giả sử các features khác không đổi, chỉ cập nhật Price
        new_row = temp_input[0, -1, :].copy()
        new_row[0] = predicted_price[0, 0]  # Cập nhật Price prediction

        temp_input = np.append(temp_input[:, 1:, :],
                              new_row.reshape(1, 1, input_data.shape[1]), axis=1)

    # Chuyển đổi lst_output thành numpy array và inverse transform
    lst_output = np.array(lst_output).reshape(-1, 1)
    return scaler_target.inverse_transform(lst_output)

Huấn luyện mô hình 9:1¶

In [62]:
# Tạo dữ liệu train và test với time_step = 50 cho split 9:1
X_train_91, y_train_91 = create_multivariate_time_series_data(scaled_train_input_91, scaled_train_target_91, time_step)
X_test_91, y_test_91 = create_multivariate_time_series_data(scaled_test_input_91, scaled_test_target_91, time_step)

print(f"X_train_91 shape: {X_train_91.shape}")
print(f"y_train_91 shape: {y_train_91.shape}")
print(f"X_test_91 shape: {X_test_91.shape}")
print(f"y_test_91 shape: {y_test_91.shape}")

# Xây dựng mô hình RNN cho split 9:1
model_rnn_91 = build_rnn_model_with_regularization(time_step, 3)  # 3 features: Price, Open, Vol

# Callbacks để tối ưu hóa
early_stop_91 = EarlyStopping(monitor='val_loss', patience=15, restore_best_weights=True, verbose=1)
reduce_lr_91 = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=10, min_lr=1e-7, verbose=1)

# Huấn luyện mô hình 9:1
history_rnn_91 = model_rnn_91.fit(
    X_train_91, y_train_91,
    epochs=60,
    batch_size=16,
    validation_data=(X_test_91, y_test_91),
    callbacks=[early_stop_91, reduce_lr_91],
    verbose=1
)
X_train_91 shape: (2983, 50, 3)
y_train_91 shape: (2983,)
X_test_91 shape: (287, 50, 3)
y_test_91 shape: (287,)
Epoch 1/60
c:\Users\Hii\AppData\Local\Programs\Python\Python311\Lib\site-packages\keras\src\layers\rnn\rnn.py:199: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead.
  super().__init__(**kwargs)
187/187 ━━━━━━━━━━━━━━━━━━━━ 3s 7ms/step - loss: 0.0360 - mae: 0.1110 - val_loss: 0.0089 - val_mae: 0.0467 - learning_rate: 1.0000e-04
Epoch 2/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0166 - mae: 0.0609 - val_loss: 0.0091 - val_mae: 0.0509 - learning_rate: 1.0000e-04
Epoch 3/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0142 - mae: 0.0539 - val_loss: 0.0112 - val_mae: 0.0663 - learning_rate: 1.0000e-04
Epoch 4/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0133 - mae: 0.0513 - val_loss: 0.0122 - val_mae: 0.0746 - learning_rate: 1.0000e-04
Epoch 5/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0126 - mae: 0.0490 - val_loss: 0.0120 - val_mae: 0.0735 - learning_rate: 1.0000e-04
Epoch 6/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0109 - mae: 0.0443 - val_loss: 0.0087 - val_mae: 0.0506 - learning_rate: 1.0000e-04
Epoch 7/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0112 - mae: 0.0460 - val_loss: 0.0151 - val_mae: 0.0928 - learning_rate: 1.0000e-04
Epoch 8/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0105 - mae: 0.0429 - val_loss: 0.0082 - val_mae: 0.0481 - learning_rate: 1.0000e-04
Epoch 9/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0101 - mae: 0.0415 - val_loss: 0.0126 - val_mae: 0.0800 - learning_rate: 1.0000e-04
Epoch 10/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0094 - mae: 0.0390 - val_loss: 0.0117 - val_mae: 0.0754 - learning_rate: 1.0000e-04
Epoch 11/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0098 - mae: 0.0417 - val_loss: 0.0076 - val_mae: 0.0442 - learning_rate: 1.0000e-04
Epoch 12/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0088 - mae: 0.0385 - val_loss: 0.0074 - val_mae: 0.0427 - learning_rate: 1.0000e-04
Epoch 13/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0092 - mae: 0.0390 - val_loss: 0.0087 - val_mae: 0.0554 - learning_rate: 1.0000e-04
Epoch 14/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0093 - mae: 0.0400 - val_loss: 0.0087 - val_mae: 0.0570 - learning_rate: 1.0000e-04
Epoch 15/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0086 - mae: 0.0393 - val_loss: 0.0076 - val_mae: 0.0481 - learning_rate: 1.0000e-04
Epoch 16/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0077 - mae: 0.0354 - val_loss: 0.0088 - val_mae: 0.0587 - learning_rate: 1.0000e-04
Epoch 17/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0076 - mae: 0.0349 - val_loss: 0.0069 - val_mae: 0.0434 - learning_rate: 1.0000e-04
Epoch 18/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0076 - mae: 0.0355 - val_loss: 0.0059 - val_mae: 0.0318 - learning_rate: 1.0000e-04
Epoch 19/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0077 - mae: 0.0356 - val_loss: 0.0076 - val_mae: 0.0511 - learning_rate: 1.0000e-04
Epoch 20/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0076 - mae: 0.0362 - val_loss: 0.0068 - val_mae: 0.0450 - learning_rate: 1.0000e-04
Epoch 21/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0075 - mae: 0.0357 - val_loss: 0.0066 - val_mae: 0.0429 - learning_rate: 1.0000e-04
Epoch 22/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0072 - mae: 0.0361 - val_loss: 0.0083 - val_mae: 0.0587 - learning_rate: 1.0000e-04
Epoch 23/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0070 - mae: 0.0344 - val_loss: 0.0090 - val_mae: 0.0652 - learning_rate: 1.0000e-04
Epoch 24/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0072 - mae: 0.0349 - val_loss: 0.0068 - val_mae: 0.0470 - learning_rate: 1.0000e-04
Epoch 25/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0068 - mae: 0.0345 - val_loss: 0.0061 - val_mae: 0.0418 - learning_rate: 1.0000e-04
Epoch 26/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0066 - mae: 0.0332 - val_loss: 0.0066 - val_mae: 0.0480 - learning_rate: 1.0000e-04
Epoch 27/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0063 - mae: 0.0328 - val_loss: 0.0083 - val_mae: 0.0630 - learning_rate: 1.0000e-04
Epoch 28/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0062 - mae: 0.0326 - val_loss: 0.0055 - val_mae: 0.0381 - learning_rate: 1.0000e-04
Epoch 29/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0065 - mae: 0.0334 - val_loss: 0.0067 - val_mae: 0.0505 - learning_rate: 1.0000e-04
Epoch 30/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0064 - mae: 0.0340 - val_loss: 0.0050 - val_mae: 0.0330 - learning_rate: 1.0000e-04
Epoch 31/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0059 - mae: 0.0316 - val_loss: 0.0053 - val_mae: 0.0380 - learning_rate: 1.0000e-04
Epoch 32/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0059 - mae: 0.0313 - val_loss: 0.0063 - val_mae: 0.0496 - learning_rate: 1.0000e-04
Epoch 33/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0059 - mae: 0.0333 - val_loss: 0.0071 - val_mae: 0.0557 - learning_rate: 1.0000e-04
Epoch 34/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0056 - mae: 0.0317 - val_loss: 0.0047 - val_mae: 0.0331 - learning_rate: 1.0000e-04
Epoch 35/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0055 - mae: 0.0317 - val_loss: 0.0057 - val_mae: 0.0447 - learning_rate: 1.0000e-04
Epoch 36/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0054 - mae: 0.0308 - val_loss: 0.0045 - val_mae: 0.0330 - learning_rate: 1.0000e-04
Epoch 37/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0054 - mae: 0.0324 - val_loss: 0.0055 - val_mae: 0.0434 - learning_rate: 1.0000e-04
Epoch 38/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0052 - mae: 0.0309 - val_loss: 0.0052 - val_mae: 0.0424 - learning_rate: 1.0000e-04
Epoch 39/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0053 - mae: 0.0312 - val_loss: 0.0048 - val_mae: 0.0370 - learning_rate: 1.0000e-04
Epoch 40/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0050 - mae: 0.0303 - val_loss: 0.0048 - val_mae: 0.0381 - learning_rate: 1.0000e-04
Epoch 41/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0049 - mae: 0.0299 - val_loss: 0.0037 - val_mae: 0.0255 - learning_rate: 1.0000e-04
Epoch 42/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0047 - mae: 0.0294 - val_loss: 0.0046 - val_mae: 0.0373 - learning_rate: 1.0000e-04
Epoch 43/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0049 - mae: 0.0312 - val_loss: 0.0035 - val_mae: 0.0247 - learning_rate: 1.0000e-04
Epoch 44/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0048 - mae: 0.0305 - val_loss: 0.0036 - val_mae: 0.0263 - learning_rate: 1.0000e-04
Epoch 45/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0046 - mae: 0.0302 - val_loss: 0.0039 - val_mae: 0.0314 - learning_rate: 1.0000e-04
Epoch 46/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0043 - mae: 0.0281 - val_loss: 0.0039 - val_mae: 0.0314 - learning_rate: 1.0000e-04
Epoch 47/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0044 - mae: 0.0295 - val_loss: 0.0039 - val_mae: 0.0328 - learning_rate: 1.0000e-04
Epoch 48/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0042 - mae: 0.0288 - val_loss: 0.0036 - val_mae: 0.0296 - learning_rate: 1.0000e-04
Epoch 49/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0044 - mae: 0.0292 - val_loss: 0.0042 - val_mae: 0.0377 - learning_rate: 1.0000e-04
Epoch 50/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0041 - mae: 0.0282 - val_loss: 0.0036 - val_mae: 0.0295 - learning_rate: 1.0000e-04
Epoch 51/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0043 - mae: 0.0304 - val_loss: 0.0036 - val_mae: 0.0306 - learning_rate: 1.0000e-04
Epoch 52/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0043 - mae: 0.0299 - val_loss: 0.0040 - val_mae: 0.0366 - learning_rate: 1.0000e-04
Epoch 53/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0043 - mae: 0.0307 - val_loss: 0.0027 - val_mae: 0.0199 - learning_rate: 1.0000e-04
Epoch 54/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0043 - mae: 0.0304 - val_loss: 0.0030 - val_mae: 0.0249 - learning_rate: 1.0000e-04
Epoch 55/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0039 - mae: 0.0281 - val_loss: 0.0034 - val_mae: 0.0312 - learning_rate: 1.0000e-04
Epoch 56/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0038 - mae: 0.0286 - val_loss: 0.0029 - val_mae: 0.0238 - learning_rate: 1.0000e-04
Epoch 57/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0038 - mae: 0.0285 - val_loss: 0.0032 - val_mae: 0.0296 - learning_rate: 1.0000e-04
Epoch 58/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0038 - mae: 0.0290 - val_loss: 0.0027 - val_mae: 0.0214 - learning_rate: 1.0000e-04
Epoch 59/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0037 - mae: 0.0288 - val_loss: 0.0030 - val_mae: 0.0274 - learning_rate: 1.0000e-04
Epoch 60/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0037 - mae: 0.0287 - val_loss: 0.0025 - val_mae: 0.0209 - learning_rate: 1.0000e-04
Restoring model weights from the end of the best epoch: 60.

Đánh giá mô hình 9:1¶

In [63]:
# Vẽ val_loss để đánh giá overfitting cho split 9:1
plt.figure(figsize=(12, 8))

plt.subplot(2, 1, 1)
plt.plot(history_rnn_91.history['loss'], label='Training Loss', linewidth=2)
plt.plot(history_rnn_91.history['val_loss'], label='Validation Loss', linewidth=2)
plt.title('RNN Model Loss 9:1 Split - Training vs Validation')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# Tìm epoch có val_loss thấp nhất cho split 9:1
best_epoch_91 = np.argmin(history_rnn_91.history['val_loss']) + 1
best_val_loss_91 = min(history_rnn_91.history['val_loss'])
print(f"Epoch tốt nhất (9:1): {best_epoch_91} với val_loss: {best_val_loss_91:.6f}")
No description has been provided for this image
Epoch tốt nhất (9:1): 60 với val_loss: 0.002550

Dự đoán và trực quan hóa 9:1¶

In [64]:
# Dự đoán 30, 60, 90 ngày tiếp theo cho split 9:1
last_data_scaled_91 = scaled_test_input_91[-time_step:]

forecasted_prices_30_91 = forecast_multivariate_prices(model_rnn_91, last_data_scaled_91, time_step, 30, scaler_target)
forecasted_prices_60_91 = forecast_multivariate_prices(model_rnn_91, last_data_scaled_91, time_step, 60, scaler_target)
forecasted_prices_90_91 = forecast_multivariate_prices(model_rnn_91, last_data_scaled_91, time_step, 90, scaler_target)

# Dự đoán giá trên tập kiểm tra cho split 9:1
test_predict_scaled_91 = model_rnn_91.predict(X_test_91)
test_predict_rnn_91 = scaler_target.inverse_transform(test_predict_scaled_91)

# Tạo DataFrame cho các dự đoán 9:1
forecast_dates_30_91 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=30, freq='D')
forecast_dates_60_91 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=60, freq='D')
forecast_dates_90_91 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=90, freq='D')

forecast_df_30_91 = pd.DataFrame(forecasted_prices_30_91, index=forecast_dates_30_91, columns=['Price'])
forecast_df_60_91 = pd.DataFrame(forecasted_prices_60_91, index=forecast_dates_60_91, columns=['Price'])
forecast_df_90_91 = pd.DataFrame(forecasted_prices_90_91, index=forecast_dates_90_91, columns=['Price'])

# Trực quan hóa kết quả cho split 9:1
plt.figure(figsize=(16, 10))

# Vẽ giá thực tế
plt.plot(data.index, data['Price'], label='Giá thực tế', color='blue', linewidth=2, alpha=0.8)

# Vẽ dự đoán trên tập test 9:1
plt.plot(test_data_91.index[time_step:], test_predict_rnn_91,
         label='Dự đoán trên tập test (9:1)', color='orange', linewidth=2, alpha=0.8)

# Vẽ các dự đoán tương lai 9:1
plt.plot(forecast_df_30_91.index, forecast_df_30_91['Price'],
         label='Dự đoán 30 ngày tiếp theo (9:1)', color='red', linestyle='--', linewidth=2, alpha=0.7)

plt.plot(forecast_df_60_91.index, forecast_df_60_91['Price'],
         label='Dự đoán 60 ngày tiếp theo (9:1)', color='green', linestyle='--', linewidth=2, alpha=0.5)

plt.plot(forecast_df_90_91.index, forecast_df_90_91['Price'],
         label='Dự đoán 90 ngày tiếp theo (9:1)', color='purple', linestyle='--', linewidth=2, alpha=0.3)

# Thêm đường thẳng đứng để phân biệt vùng dữ liệu
plt.axvline(x=data.index[-1], color='black', linestyle=':', alpha=0.7,
            label='Ngày cuối cùng của dữ liệu thực tế')

plt.title(f'Dự đoán giá ETH bằng RNN (9:1 Split, Time Step = {time_step})\nDự đoán 30, 60, 90 ngày tiếp theo',
          fontsize=16, fontweight='bold')
plt.xlabel('Ngày', fontsize=12)
plt.ylabel('Giá (USD)', fontsize=12)
plt.legend(fontsize=10, loc='upper left')
plt.grid(True, alpha=0.3)

plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
9/9 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step 
No description has been provided for this image
In [65]:
# Đánh giá mô hình 9:1
# Lấy giá trị thực tế trên tập test 9:1
y_test_actual_91 = test_data_91['Price'].values[time_step:]

# Tính toán các metrics cho split 9:1
mape_91 = mean_absolute_percentage_error(y_test_actual_91, test_predict_rnn_91.flatten())
mse_91 = mean_squared_error(y_test_actual_91, test_predict_rnn_91.flatten())
rmse_91 = np.sqrt(mse_91)

print(f'Kết quả đánh giá mô hình RNN 9:1 Split (Time Step = {time_step}):')
print(f'MAPE: {mape_91:.2f}%')
print(f'MSE: {mse_91:.2f}')
print(f'RMSE: {rmse_91:.2f}')
print(f'Số epochs huấn luyện: {len(history_rnn_91.history["loss"])}')

# Hiển thị thông tin dự đoán 30 ngày cho 9:1
print(f'\nDự đoán giá ETH 30 ngày tiếp theo (9:1):')
print(f'Giá cao nhất: ${forecasted_prices_30_91.max():.2f}')
print(f'Giá thấp nhất: ${forecasted_prices_30_91.min():.2f}')
print(f'Giá trung bình: ${forecasted_prices_30_91.mean():.2f}')
Kết quả đánh giá mô hình RNN 9:1 Split (Time Step = 50):
MAPE: 0.04%
MSE: 18096.69
RMSE: 134.52
Số epochs huấn luyện: 60

Dự đoán giá ETH 30 ngày tiếp theo (9:1):
Giá cao nhất: $2512.60
Giá thấp nhất: $2424.56
Giá trung bình: $2476.96

So sánh 3 tỉ lệ¶

In [66]:
# So sánh chi tiết giữa 3 tỉ lệ chia dữ liệu
print("="*80)
print("SO SÁNH CHI TIẾT GIỮA 3 TỈ LỆ CHIA DỮ LIỆU")
print("="*80)

# Thu thập thông tin từ 3 splits
splits_info = {
    '7:3': {
        'train_size': len(train_data),
        'test_size': len(test_data),
        'mape': mape,
        'mse': mse,
        'rmse': rmse,
        'epochs': len(history_rnn.history['loss']),
        'best_val_loss': best_val_loss,
        'best_epoch': best_epoch,
        'final_train_loss': history_rnn.history['loss'][-1],
        'final_val_loss': history_rnn.history['val_loss'][-1]
    },
    '8:2': {
        'train_size': len(train_data_82),
        'test_size': len(test_data_82),
        'mape': mape_82,
        'mse': mse_82,
        'rmse': rmse_82,
        'epochs': len(history_rnn_82.history['loss']),
        'best_val_loss': best_val_loss_82,
        'best_epoch': best_epoch_82,
        'final_train_loss': history_rnn_82.history['loss'][-1],
        'final_val_loss': history_rnn_82.history['val_loss'][-1]
    },
    '9:1': {
        'train_size': len(train_data_91),
        'test_size': len(test_data_91),
        'mape': mape_91,
        'mse': mse_91,
        'rmse': rmse_91,
        'epochs': len(history_rnn_91.history['loss']),
        'best_val_loss': best_val_loss_91,
        'best_epoch': best_epoch_91,
        'final_train_loss': history_rnn_91.history['loss'][-1],
        'final_val_loss': history_rnn_91.history['val_loss'][-1]
    }
}

# In bảng so sánh
for split, info in splits_info.items():
    print(f"\n{split} Split:")
    print(f"  Kích thước train: {info['train_size']:,} mẫu")
    print(f"  Kích thước test: {info['test_size']:,} mẫu")
    print(f"  MAPE: {info['mape']:.2f}%")
    print(f"  MSE: {info['mse']:,.2f}")
    print(f"  RMSE: {info['rmse']:,.2f}")
    print(f"  Số epochs: {info['epochs']}")
    print(f"  Best val_loss: {info['best_val_loss']:.6f} (epoch {info['best_epoch']})")
    print(f"  Final train_loss: {info['final_train_loss']:.6f}")
    print(f"  Final val_loss: {info['final_val_loss']:.6f}")
    print(f"  Overfitting gap: {abs(info['final_val_loss'] - info['final_train_loss']):.6f}")
================================================================================
SO SÁNH CHI TIẾT GIỮA 3 TỈ LỆ CHIA DỮ LIỆU
================================================================================

7:3 Split:
  Kích thước train: 2,359 mẫu
  Kích thước test: 1,011 mẫu
  MAPE: 0.03%
  MSE: 15,641.58
  RMSE: 125.07
  Số epochs: 60
  Best val_loss: 0.003579 (epoch 58)
  Final train_loss: 0.005304
  Final val_loss: 0.003708
  Overfitting gap: 0.001596

8:2 Split:
  Kích thước train: 2,696 mẫu
  Kích thước test: 674 mẫu
  MAPE: 0.03%
  MSE: 14,668.50
  RMSE: 121.11
  Số epochs: 60
  Best val_loss: 0.002610 (epoch 60)
  Final train_loss: 0.003832
  Final val_loss: 0.002610
  Overfitting gap: 0.001222

9:1 Split:
  Kích thước train: 3,033 mẫu
  Kích thước test: 337 mẫu
  MAPE: 0.04%
  MSE: 18,096.69
  RMSE: 134.52
  Số epochs: 60
  Best val_loss: 0.002550 (epoch 60)
  Final train_loss: 0.003736
  Final val_loss: 0.002550
  Overfitting gap: 0.001186
In [67]:
# Vẽ biểu đồ so sánh các metrics
fig, axes = plt.subplots(2, 3, figsize=(18, 12))

splits = ['7:3', '8:2', '9:1']
colors = ['#1f77b4', '#ff7f0e', '#2ca02c']

# 1. So sánh MAPE
mape_values = [splits_info[split]['mape'] for split in splits]
axes[0, 0].bar(splits, mape_values, color=colors, alpha=0.7)
axes[0, 0].set_title('So sánh MAPE (%)', fontsize=14, fontweight='bold')
axes[0, 0].set_ylabel('MAPE (%)')
axes[0, 0].grid(True, alpha=0.3)
for i, v in enumerate(mape_values):
    axes[0, 0].text(i, v + 0.1, f'{v:.2f}%', ha='center', va='bottom', fontweight='bold')

# 2. So sánh RMSE
rmse_values = [splits_info[split]['rmse'] for split in splits]
axes[0, 1].bar(splits, rmse_values, color=colors, alpha=0.7)
axes[0, 1].set_title('So sánh RMSE (USD)', fontsize=14, fontweight='bold')
axes[0, 1].set_ylabel('RMSE (USD)')
axes[0, 1].grid(True, alpha=0.3)
for i, v in enumerate(rmse_values):
    axes[0, 1].text(i, v + 200, f'{v:,.0f}', ha='center', va='bottom', fontweight='bold')

# 3. So sánh Best Validation Loss
best_val_loss_values = [splits_info[split]['best_val_loss'] for split in splits]
axes[0, 2].bar(splits, best_val_loss_values, color=colors, alpha=0.7)
axes[0, 2].set_title('So sánh Best Validation Loss', fontsize=14, fontweight='bold')
axes[0, 2].set_ylabel('Best Val Loss')
axes[0, 2].grid(True, alpha=0.3)
for i, v in enumerate(best_val_loss_values):
    axes[0, 2].text(i, v + 0.0001, f'{v:.4f}', ha='center', va='bottom', fontweight='bold')

# 4. So sánh số epochs
epochs_values = [splits_info[split]['epochs'] for split in splits]
axes[1, 0].bar(splits, epochs_values, color=colors, alpha=0.7)
axes[1, 0].set_title('So sánh Số Epochs', fontsize=14, fontweight='bold')
axes[1, 0].set_ylabel('Số Epochs')
axes[1, 0].grid(True, alpha=0.3)
for i, v in enumerate(epochs_values):
    axes[1, 0].text(i, v + 0.5, f'{v}', ha='center', va='bottom', fontweight='bold')

# 5. So sánh Overfitting Gap
overfitting_gap = [abs(splits_info[split]['final_val_loss'] - splits_info[split]['final_train_loss']) 
                   for split in splits]
axes[1, 1].bar(splits, overfitting_gap, color=colors, alpha=0.7)
axes[1, 1].set_title('So sánh Overfitting Gap', fontsize=14, fontweight='bold')
axes[1, 1].set_ylabel('|Val Loss - Train Loss|')
axes[1, 1].grid(True, alpha=0.3)
for i, v in enumerate(overfitting_gap):
    axes[1, 1].text(i, v + 0.001, f'{v:.4f}', ha='center', va='bottom', fontweight='bold')

# 6. So sánh kích thước test set
test_sizes = [splits_info[split]['test_size'] for split in splits]
axes[1, 2].bar(splits, test_sizes, color=colors, alpha=0.7)
axes[1, 2].set_title('So sánh Kích thước Test Set', fontsize=14, fontweight='bold')
axes[1, 2].set_ylabel('Số mẫu test')
axes[1, 2].grid(True, alpha=0.3)
for i, v in enumerate(test_sizes):
    axes[1, 2].text(i, v + 20, f'{v:,}', ha='center', va='bottom', fontweight='bold')

plt.tight_layout()
plt.show()
C:\Users\Hii\AppData\Local\Temp\ipykernel_28116\2283599594.py:62: UserWarning: Tight layout not applied. tight_layout cannot make Axes height small enough to accommodate all Axes decorations.
  plt.tight_layout()
No description has been provided for this image
In [68]:
# Vẽ so sánh Training và Validation Loss curves
plt.figure(figsize=(18, 6))

# Subplot 1: 7:3 Split
plt.subplot(1, 3, 1)
plt.plot(history_rnn.history['loss'], label='Training Loss', linewidth=2, color='blue')
plt.plot(history_rnn.history['val_loss'], label='Validation Loss', linewidth=2, color='red')
plt.title('7:3 Split - Loss Curves', fontsize=14, fontweight='bold')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.axvline(x=best_epoch-1, color='green', linestyle='--', alpha=0.7, label=f'Best Epoch: {best_epoch}')

# Subplot 2: 8:2 Split
plt.subplot(1, 3, 2)
plt.plot(history_rnn_82.history['loss'], label='Training Loss', linewidth=2, color='blue')
plt.plot(history_rnn_82.history['val_loss'], label='Validation Loss', linewidth=2, color='red')
plt.title('8:2 Split - Loss Curves', fontsize=14, fontweight='bold')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.axvline(x=best_epoch_82-1, color='green', linestyle='--', alpha=0.7, label=f'Best Epoch: {best_epoch_82}')

# Subplot 3: 9:1 Split
plt.subplot(1, 3, 3)
plt.plot(history_rnn_91.history['loss'], label='Training Loss', linewidth=2, color='blue')
plt.plot(history_rnn_91.history['val_loss'], label='Validation Loss', linewidth=2, color='red')
plt.title('9:1 Split - Loss Curves', fontsize=14, fontweight='bold')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.axvline(x=best_epoch_91-1, color='green', linestyle='--', alpha=0.7, label=f'Best Epoch: {best_epoch_91}')

plt.tight_layout()
plt.show()
No description has been provided for this image
In [69]:
# Tạo DataFrame tổng hợp kết quả để dễ so sánh
import pandas as pd

comparison_df = pd.DataFrame({
    'Split': ['7:3', '8:2', '9:1'],
    'Train_Size': [splits_info[split]['train_size'] for split in splits],
    'Test_Size': [splits_info[split]['test_size'] for split in splits],
    'MAPE (%)': [splits_info[split]['mape'] for split in splits],
    'RMSE (USD)': [splits_info[split]['rmse'] for split in splits],
    'MSE': [splits_info[split]['mse'] for split in splits],
    'Best_Val_Loss': [splits_info[split]['best_val_loss'] for split in splits],
    'Best_Epoch': [splits_info[split]['best_epoch'] for split in splits],
    'Total_Epochs': [splits_info[split]['epochs'] for split in splits],
    'Overfitting_Gap': [abs(splits_info[split]['final_val_loss'] - splits_info[split]['final_train_loss']) 
                        for split in splits]
})

print("\nBẢNG TỔNG HỢP KẾT QUẢ:")
print("="*100)
print(comparison_df.to_string(index=False, float_format='%.4f'))
BẢNG TỔNG HỢP KẾT QUẢ:
====================================================================================================
Split  Train_Size  Test_Size  MAPE (%)  RMSE (USD)        MSE  Best_Val_Loss  Best_Epoch  Total_Epochs  Overfitting_Gap
  7:3        2359       1011    0.0349    125.0663 15641.5773         0.0036          58            60           0.0016
  8:2        2696        674    0.0314    121.1136 14668.5044         0.0026          60            60           0.0012
  9:1        3033        337    0.0366    134.5239 18096.6919         0.0025          60            60           0.0012
In [70]:
# Phân tích và đưa ra khuyến nghị
print("\n" + "="*80)
print("PHÂN TÍCH VÀ KHUYẾN NGHỊ")
print("="*80)

# Tìm split tốt nhất cho từng metric
best_mape_split = splits[np.argmin([splits_info[split]['mape'] for split in splits])]
best_rmse_split = splits[np.argmin([splits_info[split]['rmse'] for split in splits])]
best_val_loss_split = splits[np.argmin([splits_info[split]['best_val_loss'] for split in splits])]
best_overfitting_split = splits[np.argmin([abs(splits_info[split]['final_val_loss'] - splits_info[split]['final_train_loss']) 
                                          for split in splits])]

print(f"\n1. PHÂN TÍCH THEO TỪNG TIÊU CHÍ:")
print(f"   • Tốt nhất theo MAPE: {best_mape_split} ({splits_info[best_mape_split]['mape']:.2f}%)")
print(f"   • Tốt nhất theo RMSE: {best_rmse_split} ({splits_info[best_rmse_split]['rmse']:,.2f} USD)")
print(f"   • Tốt nhất theo Val Loss: {best_val_loss_split} ({splits_info[best_val_loss_split]['best_val_loss']:.6f})")
print(f"   • Ít overfitting nhất: {best_overfitting_split} (gap: {abs(splits_info[best_overfitting_split]['final_val_loss'] - splits_info[best_overfitting_split]['final_train_loss']):.6f})")

# Tính điểm tổng hợp (rank-based scoring)
def calculate_rank_score(splits_info):
    scores = {}
    splits_list = list(splits_info.keys())
    
    # Rank cho MAPE (thấp hơn = tốt hơn)
    mape_rank = sorted(splits_list, key=lambda x: splits_info[x]['mape'])
    # Rank cho RMSE (thấp hơn = tốt hơn)
    rmse_rank = sorted(splits_list, key=lambda x: splits_info[x]['rmse'])
    # Rank cho Best Val Loss (thấp hơn = tốt hơn)
    val_loss_rank = sorted(splits_list, key=lambda x: splits_info[x]['best_val_loss'])
    # Rank cho Overfitting Gap (thấp hơn = tốt hơn)
    overfitting_rank = sorted(splits_list, key=lambda x: abs(splits_info[x]['final_val_loss'] - splits_info[x]['final_train_loss']))
    
    for split in splits_list:
        # Điểm rank (1 = tốt nhất, 3 = kém nhất)
        score = (mape_rank.index(split) + 1) * 0.3 + \
                (rmse_rank.index(split) + 1) * 0.3 + \
                (val_loss_rank.index(split) + 1) * 0.25 + \
                (overfitting_rank.index(split) + 1) * 0.15
        scores[split] = score
    
    return scores

# Tính điểm tổng hợp
scores = calculate_rank_score(splits_info)
best_overall_split = min(scores, key=scores.get)
print(f"\n2. ĐIỂM TỔNG HỢP (trọng số: MAPE=30%, RMSE=30%, Val_Loss=25%, Overfitting=15%):")
for split in splits_info.keys():
    print(f"   • {split}: {scores[split]:.2f} điểm")
    print(f"   • {split}: {scores[split]:.2f} điểm")

print(f"\n3. KẾT LUẬN VÀ KHUYẾN NGHỊ:")
print(f"   🏆 MÔ HÌNH TỐT NHẤT: Split {best_overall_split}")
print(f"   📊 Lý do:")
print(f"      - MAPE: {splits_info[best_overall_split]['mape']:.2f}%")
print(f"      - RMSE: {splits_info[best_overall_split]['rmse']:,.2f} USD")
print(f"      - Best Val Loss: {splits_info[best_overall_split]['best_val_loss']:.6f}")
print(f"      - Overfitting Gap: {abs(splits_info[best_overall_split]['final_val_loss'] - splits_info[best_overall_split]['final_train_loss']):.6f}")
print(f"      - Tập test có {splits_info[best_overall_split]['test_size']:,} mẫu (đủ để đánh giá)")
print(f"      - Huấn luyện ổn định với {splits_info[best_overall_split]['epochs']} epochs")

print(f"\n4. NHẬN XÉT CHUNG:")
if best_overall_split == '7:3':
    print("   • Split 7:3 cân bằng tốt giữa kích thước tập train và test")
    print("   • Tập test đủ lớn để đánh giá độ tin cậy của mô hình")
    print("   • Hiệu suất dự đoán tốt với mức overfitting chấp nhận được")
elif best_overall_split == '8:2':
    print("   • Split 8:2 có nhiều dữ liệu train hơn, giúp mô hình học tốt hơn")
    print("   • Tập test vẫn đủ lớn để đánh giá")
    print("   • Cân bằng tốt giữa hiệu suất và độ tin cậy")
else:  # 9:1
    print("   • Split 9:1 tối đa hóa dữ liệu train")
    print("   • Có thể có rủi ro về độ tin cậy do tập test nhỏ")
    print("   • Phù hợp khi có ít dữ liệu và cần tối ưu hiệu suất")

print(f"\n   ⚠️  LƯU Ý: Với dữ liệu time series như ETH, nên chọn split cân bằng")
print(f"   để đảm bảo tập test đủ lớn và đại diện cho nhiều giai đoạn thị trường khác nhau.")
================================================================================
PHÂN TÍCH VÀ KHUYẾN NGHỊ
================================================================================

1. PHÂN TÍCH THEO TỪNG TIÊU CHÍ:
   • Tốt nhất theo MAPE: 8:2 (0.03%)
   • Tốt nhất theo RMSE: 8:2 (121.11 USD)
   • Tốt nhất theo Val Loss: 9:1 (0.002550)
   • Ít overfitting nhất: 9:1 (gap: 0.001186)

2. ĐIỂM TỔNG HỢP (trọng số: MAPE=30%, RMSE=30%, Val_Loss=25%, Overfitting=15%):
   • 7:3: 2.40 điểm
   • 7:3: 2.40 điểm
   • 8:2: 1.40 điểm
   • 8:2: 1.40 điểm
   • 9:1: 2.20 điểm
   • 9:1: 2.20 điểm

3. KẾT LUẬN VÀ KHUYẾN NGHỊ:
   🏆 MÔ HÌNH TỐT NHẤT: Split 8:2
   📊 Lý do:
      - MAPE: 0.03%
      - RMSE: 121.11 USD
      - Best Val Loss: 0.002610
      - Overfitting Gap: 0.001222
      - Tập test có 674 mẫu (đủ để đánh giá)
      - Huấn luyện ổn định với 60 epochs

4. NHẬN XÉT CHUNG:
   • Split 8:2 có nhiều dữ liệu train hơn, giúp mô hình học tốt hơn
   • Tập test vẫn đủ lớn để đánh giá
   • Cân bằng tốt giữa hiệu suất và độ tin cậy

   ⚠️  LƯU Ý: Với dữ liệu time series như ETH, nên chọn split cân bằng
   để đảm bảo tập test đủ lớn và đại diện cho nhiều giai đoạn thị trường khác nhau.
In [71]:
# Vẽ biểu đồ radar cho so sánh tổng thể
import numpy as np
import matplotlib.pyplot as plt

def create_radar_chart():
    # Chuẩn hóa các metrics về scale 0-1 (1 là tốt nhất)
    metrics = ['MAPE', 'RMSE', 'Val_Loss', 'Overfitting', 'Test_Size']
    
    # Lấy giá trị của từng metric (đảo ngược để 1 là tốt nhất)
    data = {}
    for split in splits:
        mape_norm = 1 - (splits_info[split]['mape'] - min([splits_info[s]['mape'] for s in splits])) / \
                   (max([splits_info[s]['mape'] for s in splits]) - min([splits_info[s]['mape'] for s in splits]))
        
        rmse_norm = 1 - (splits_info[split]['rmse'] - min([splits_info[s]['rmse'] for s in splits])) / \
                   (max([splits_info[s]['rmse'] for s in splits]) - min([splits_info[s]['rmse'] for s in splits]))
        
        val_loss_norm = 1 - (splits_info[split]['best_val_loss'] - min([splits_info[s]['best_val_loss'] for s in splits])) / \
                       (max([splits_info[s]['best_val_loss'] for s in splits]) - min([splits_info[s]['best_val_loss'] for s in splits]))
        
        overfitting_gaps = [abs(splits_info[s]['final_val_loss'] - splits_info[s]['final_train_loss']) for s in splits]
        overfitting_norm = 1 - (abs(splits_info[split]['final_val_loss'] - splits_info[split]['final_train_loss']) - min(overfitting_gaps)) / \
                          (max(overfitting_gaps) - min(overfitting_gaps))
        
        test_size_norm = (splits_info[split]['test_size'] - min([splits_info[s]['test_size'] for s in splits])) / \
                        (max([splits_info[s]['test_size'] for s in splits]) - min([splits_info[s]['test_size'] for s in splits]))
        
        data[split] = [mape_norm, rmse_norm, val_loss_norm, overfitting_norm, test_size_norm]
    
    # Tạo radar chart
    angles = np.linspace(0, 2 * np.pi, len(metrics), endpoint=False).tolist()
    angles += angles[:1]  # Complete the circle
    
    fig, ax = plt.subplots(figsize=(10, 10), subplot_kw=dict(projection='polar'))
    
    colors = ['#1f77b4', '#ff7f0e', '#2ca02c']
    
    for i, (split, values) in enumerate(data.items()):
        values += values[:1]  # Complete the circle
        ax.plot(angles, values, 'o-', linewidth=2, label=f'Split {split}', color=colors[i])
        ax.fill(angles, values, alpha=0.25, color=colors[i])
    
    ax.set_xticks(angles[:-1])
    ax.set_xticklabels(metrics)
    ax.set_ylim(0, 1)
    ax.set_title('So sánh tổng thể các Split (1 = Tốt nhất)', size=16, fontweight='bold', pad=20)
    ax.legend(loc='upper right', bbox_to_anchor=(1.2, 1.0))
    ax.grid(True)
    
    plt.tight_layout()
    plt.show()

create_radar_chart()
No description has been provided for this image

Kết luận cuối cùng¶

Dựa trên phân tích toàn diện các tiêu chí đánh giá, mô hình RNN với split tỉ lệ dữ liệu tốt nhất đã được xác định.

Các yếu tố được xem xét:

  • MAPE (Mean Absolute Percentage Error): Đo lường độ chính xác dự đoán
  • RMSE (Root Mean Square Error): Đo lường sai số tuyệt đối
  • Validation Loss: Đo lường hiệu suất trên tập validation
  • Overfitting Gap: Đo lường mức độ overfitting
  • Kích thước tập test: Đảm bảo độ tin cậy trong đánh giá

Khuyến nghị sử dụng: Mô hình với tỉ lệ chia dữ liệu được đánh giá cao nhất sẽ được sử dụng cho các dự đoán cuối cùng về giá ETH.

XRP¶

Import thư viện¶

In [72]:
import numpy as np
import pandas as pd
import yfinance as yf
import datetime as dt
import matplotlib.pyplot as plt
import math
from keras.models import Sequential
from keras.layers import SimpleRNN, Dense, Dropout
from keras.optimizers import Adam
from keras.callbacks import EarlyStopping, ReduceLROnPlateau
from keras import regularizers
from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, mean_absolute_percentage_error
from typing import Tuple

XRP Dataset¶

Import csv¶

In [73]:
# Đọc file XRP
file_path = "D:\\github_desktop\\Cryptocurrency-Price-Prediction\\Cryptocurrency\\Dataset\\XRP Historical Data.csv"

data = pd.read_csv(file_path)

# Loại bỏ dấu phẩy và chuyển đổi thành float cho các cột Price và Open
for col in ['Price', 'Open']:
    data[col] = data[col].astype(str).str.replace(',', '', regex=False).astype(float)

# Hàm xử lý cột 'Vol.' chứa hậu tố 'K', 'M', 'B'
def convert_volume(val):
    val = str(val).replace(',', '').strip()
    if 'K' in val:
        return float(val.replace('K', '')) * 1_000
    elif 'M' in val:
        return float(val.replace('M', '')) * 1_000_000
    elif 'B' in val:
        return float(val.replace('B', '')) * 1_000_000_000
    else:
        try:
            return float(val)
        except ValueError:
            return np.nan  # Nếu chuỗi rỗng hoặc không hợp lệ

# Áp dụng xử lý cho cột 'Vol.'
data['Vol.'] = data['Vol.'].apply(convert_volume)

# Kiểm tra NaN trước xử lý
print(f"Trước khi xử lý, số NaN ở Vol.: {data['Vol.'].isna().sum()}")

# Nội suy và điền 0 nếu còn thiếu
data['Vol.'] = data['Vol.'].interpolate(method='linear')
data['Vol.'] = data['Vol.'].fillna(0)

# Kiểm tra NaN sau xử lý
print(f"Sau khi xử lý, số NaN ở Vol.: {data['Vol.'].isna().sum()}")

# Chuyển cột Date sang datetime và đặt làm index
data['Date'] = pd.to_datetime(data['Date'])
data.set_index('Date', inplace=True)
data.sort_index(inplace=True)

# Thông tin dữ liệu
print("Data shape:", data.shape)
print("Columns:", data.columns.tolist())
print("\nFirst 5 rows:")
print(data[['Price', 'Open', 'Vol.']].head())

print(f"Tổng số dữ liệu: {len(data)} dòng")
Trước khi xử lý, số NaN ở Vol.: 12
Sau khi xử lý, số NaN ở Vol.: 0
Data shape: (3369, 6)
Columns: ['Price', 'Open', 'High', 'Low', 'Vol.', 'Change %']

First 5 rows:
             Price    Open     Vol.
Date                               
2016-03-10  0.0082  0.0081  59130.0
2016-03-11  0.0092  0.0082  25820.0
2016-03-12  0.0081  0.0092  78230.0
2016-03-13  0.0082  0.0081    620.0
2016-03-14  0.0083  0.0082  19310.0
Tổng số dữ liệu: 3369 dòng

Chia 7:3¶

Chuẩn hóa dữ liệu¶

In [74]:
# Chuẩn hóa dữ liệu
# Lấy 3 cột Price, Open, Vol. để làm đầu vào và Price làm đầu ra
input_features = data[['Price', 'Open', 'Vol.']].values
target_feature = data[['Price']].values

# Áp dụng MinMaxScaler cho input features
scaler_input = MinMaxScaler(feature_range=(0, 1))
scaled_input = scaler_input.fit_transform(input_features)

# Áp dụng MinMaxScaler cho target feature
scaler_target = MinMaxScaler(feature_range=(0, 1))
scaled_target = scaler_target.fit_transform(target_feature)
In [75]:
# Chia dữ liệu train/test theo tỷ lệ 7:3
train_size = int(len(data) * 0.7)
train_data = data.iloc[0:train_size,:]
test_data = data.iloc[train_size:len(data),:]

# Chia dữ liệu đã chuẩn hóa
scaled_train_input = scaled_input[0:train_size,:]
scaled_test_input = scaled_input[train_size:,:]
scaled_train_target = scaled_target[0:train_size,:]
scaled_test_target = scaled_target[train_size:,:]

print(f"Kích thước tập train: {len(train_data)}")
print(f"Kích thước tập test: {len(test_data)}")
Kích thước tập train: 2358
Kích thước tập test: 1011

Xây dựng mô hình RNN¶

In [76]:
def build_rnn_model_with_regularization(time_step: int, num_features: int) -> Sequential:
    """
    Xây dựng mô hình RNN với regularization

    Args:
        time_step: Số time steps để nhìn về quá khứ
        num_features: Số features đầu vào (Price, Open, Vol = 3)

    Returns:
        Sequential model
    """
    model = Sequential()

    # SimpleRNN layer với regularization
    model.add(SimpleRNN(
        units=50,                       # Số neurons
        input_shape=(time_step, num_features),  # (50, 3)
        kernel_regularizer=regularizers.l2(0.001),    # L2 regularization
        return_sequences=False           # Chỉ cần output cuối cùng
    ))

    # Dropout để tránh overfitting
    model.add(Dropout(0.3))

    # Dense layer ẩn
    model.add(Dense(32, activation='relu'))
    model.add(Dropout(0.2))

    # Output layer: dự đoán 1 giá trị (Price)
    model.add(Dense(1))

    # Optimizer với learning rate nhỏ
    optimizer = Adam(learning_rate=1e-4)
    model.compile(optimizer=optimizer, loss='mean_squared_error', metrics=['mae'])

    return model
In [77]:
# Hàm tạo dữ liệu time series với multiple features
def create_multivariate_time_series_data(input_data: np.ndarray, target_data: np.ndarray, time_step: int) -> Tuple[np.ndarray, np.ndarray]:
    X_data, y_data = [], []
    for i in range(len(input_data) - time_step):
        X_data.append(input_data[i:(i + time_step), :])  # Lấy tất cả features
        y_data.append(target_data[i + time_step, 0])     # Chỉ lấy Price
    return np.array(X_data), np.array(y_data)
In [78]:
# Hàm dự đoán với multiple features
def forecast_multivariate_prices(model: Sequential, input_data: np.ndarray, time_step: int,
                                forecast_days: int, scaler_target: MinMaxScaler) -> np.ndarray:
    temp_input = input_data[-time_step:].reshape(1, time_step, input_data.shape[1])
    lst_output = []

    for _ in range(forecast_days):
        predicted_price = model.predict(temp_input, verbose=0)
        lst_output.append(predicted_price[0].tolist())

        # Tạo input mới cho prediction tiếp theo
        # Giả sử các features khác không đổi, chỉ cập nhật Price
        new_row = temp_input[0, -1, :].copy()
        new_row[0] = predicted_price[0, 0]  # Cập nhật Price prediction

        temp_input = np.append(temp_input[:, 1:, :],
                              new_row.reshape(1, 1, input_data.shape[1]), axis=1)

    # Chuyển đổi lst_output thành numpy array và inverse transform
    lst_output = np.array(lst_output).reshape(-1, 1)
    return scaler_target.inverse_transform(lst_output)

Huấn luyện mô hình¶

In [79]:
# Tạo dữ liệu train và test với time_step = 50
time_step = 50
X_train, y_train = create_multivariate_time_series_data(scaled_train_input, scaled_train_target, time_step)
X_test, y_test = create_multivariate_time_series_data(scaled_test_input, scaled_test_target, time_step)

print(f"X_train shape: {X_train.shape}")
print(f"y_train shape: {y_train.shape}")
print(f"X_test shape: {X_test.shape}")
print(f"y_test shape: {y_test.shape}")

# Xây dựng mô hình RNN
model_rnn = build_rnn_model_with_regularization(time_step, 3)  # 3 features: Price, Open, Vol

# Callbacks để tối ưu hóa
early_stop = EarlyStopping(monitor='val_loss', patience=15, restore_best_weights=True, verbose=1)
reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=10, min_lr=1e-7, verbose=1)

# Huấn luyện mô hình
history_rnn = model_rnn.fit(
    X_train, y_train,
    epochs=60,
    batch_size=16,
    validation_data=(X_test, y_test),
    callbacks=[early_stop, reduce_lr],
    verbose=1
)
X_train shape: (2308, 50, 3)
y_train shape: (2308,)
X_test shape: (961, 50, 3)
y_test shape: (961,)
Epoch 1/60
c:\Users\Hii\AppData\Local\Programs\Python\Python311\Lib\site-packages\keras\src\layers\rnn\rnn.py:199: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead.
  super().__init__(**kwargs)
145/145 ━━━━━━━━━━━━━━━━━━━━ 2s 7ms/step - loss: 0.0341 - mae: 0.1134 - val_loss: 0.0251 - val_mae: 0.0731 - learning_rate: 1.0000e-04
Epoch 2/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0148 - mae: 0.0617 - val_loss: 0.0133 - val_mae: 0.0468 - learning_rate: 1.0000e-04
Epoch 3/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0123 - mae: 0.0531 - val_loss: 0.0092 - val_mae: 0.0324 - learning_rate: 1.0000e-04
Epoch 4/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0112 - mae: 0.0475 - val_loss: 0.0083 - val_mae: 0.0304 - learning_rate: 1.0000e-04
Epoch 5/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0105 - mae: 0.0434 - val_loss: 0.0083 - val_mae: 0.0336 - learning_rate: 1.0000e-04
Epoch 6/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0098 - mae: 0.0410 - val_loss: 0.0069 - val_mae: 0.0261 - learning_rate: 1.0000e-04
Epoch 7/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0091 - mae: 0.0384 - val_loss: 0.0077 - val_mae: 0.0317 - learning_rate: 1.0000e-04
Epoch 8/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0092 - mae: 0.0374 - val_loss: 0.0071 - val_mae: 0.0270 - learning_rate: 1.0000e-04
Epoch 9/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0081 - mae: 0.0350 - val_loss: 0.0069 - val_mae: 0.0264 - learning_rate: 1.0000e-04
Epoch 10/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0084 - mae: 0.0345 - val_loss: 0.0066 - val_mae: 0.0292 - learning_rate: 1.0000e-04
Epoch 11/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0077 - mae: 0.0325 - val_loss: 0.0064 - val_mae: 0.0259 - learning_rate: 1.0000e-04
Epoch 12/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0079 - mae: 0.0326 - val_loss: 0.0058 - val_mae: 0.0180 - learning_rate: 1.0000e-04
Epoch 13/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0074 - mae: 0.0310 - val_loss: 0.0058 - val_mae: 0.0190 - learning_rate: 1.0000e-04
Epoch 14/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0074 - mae: 0.0302 - val_loss: 0.0065 - val_mae: 0.0297 - learning_rate: 1.0000e-04
Epoch 15/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0067 - mae: 0.0284 - val_loss: 0.0061 - val_mae: 0.0245 - learning_rate: 1.0000e-04
Epoch 16/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0069 - mae: 0.0282 - val_loss: 0.0057 - val_mae: 0.0216 - learning_rate: 1.0000e-04
Epoch 17/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0065 - mae: 0.0277 - val_loss: 0.0055 - val_mae: 0.0180 - learning_rate: 1.0000e-04
Epoch 18/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0061 - mae: 0.0256 - val_loss: 0.0060 - val_mae: 0.0270 - learning_rate: 1.0000e-04
Epoch 19/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0065 - mae: 0.0277 - val_loss: 0.0050 - val_mae: 0.0167 - learning_rate: 1.0000e-04
Epoch 20/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0062 - mae: 0.0273 - val_loss: 0.0050 - val_mae: 0.0183 - learning_rate: 1.0000e-04
Epoch 21/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0058 - mae: 0.0253 - val_loss: 0.0047 - val_mae: 0.0139 - learning_rate: 1.0000e-04
Epoch 22/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0056 - mae: 0.0242 - val_loss: 0.0053 - val_mae: 0.0218 - learning_rate: 1.0000e-04
Epoch 23/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0055 - mae: 0.0247 - val_loss: 0.0050 - val_mae: 0.0212 - learning_rate: 1.0000e-04
Epoch 24/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0054 - mae: 0.0243 - val_loss: 0.0046 - val_mae: 0.0155 - learning_rate: 1.0000e-04
Epoch 25/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0055 - mae: 0.0244 - val_loss: 0.0047 - val_mae: 0.0190 - learning_rate: 1.0000e-04
Epoch 26/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0053 - mae: 0.0238 - val_loss: 0.0045 - val_mae: 0.0183 - learning_rate: 1.0000e-04
Epoch 27/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0050 - mae: 0.0230 - val_loss: 0.0047 - val_mae: 0.0199 - learning_rate: 1.0000e-04
Epoch 28/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0049 - mae: 0.0234 - val_loss: 0.0045 - val_mae: 0.0194 - learning_rate: 1.0000e-04
Epoch 29/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0050 - mae: 0.0228 - val_loss: 0.0039 - val_mae: 0.0128 - learning_rate: 1.0000e-04
Epoch 30/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0049 - mae: 0.0242 - val_loss: 0.0048 - val_mae: 0.0222 - learning_rate: 1.0000e-04
Epoch 31/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0044 - mae: 0.0211 - val_loss: 0.0041 - val_mae: 0.0169 - learning_rate: 1.0000e-04
Epoch 32/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0044 - mae: 0.0213 - val_loss: 0.0041 - val_mae: 0.0174 - learning_rate: 1.0000e-04
Epoch 33/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0042 - mae: 0.0215 - val_loss: 0.0041 - val_mae: 0.0190 - learning_rate: 1.0000e-04
Epoch 34/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0040 - mae: 0.0215 - val_loss: 0.0045 - val_mae: 0.0226 - learning_rate: 1.0000e-04
Epoch 35/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0040 - mae: 0.0215 - val_loss: 0.0035 - val_mae: 0.0145 - learning_rate: 1.0000e-04
Epoch 36/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0038 - mae: 0.0200 - val_loss: 0.0040 - val_mae: 0.0192 - learning_rate: 1.0000e-04
Epoch 37/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0039 - mae: 0.0206 - val_loss: 0.0039 - val_mae: 0.0186 - learning_rate: 1.0000e-04
Epoch 38/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0035 - mae: 0.0192 - val_loss: 0.0040 - val_mae: 0.0190 - learning_rate: 1.0000e-04
Epoch 39/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0037 - mae: 0.0210 - val_loss: 0.0036 - val_mae: 0.0168 - learning_rate: 1.0000e-04
Epoch 40/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0037 - mae: 0.0211 - val_loss: 0.0033 - val_mae: 0.0146 - learning_rate: 1.0000e-04
Epoch 41/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0034 - mae: 0.0200 - val_loss: 0.0033 - val_mae: 0.0153 - learning_rate: 1.0000e-04
Epoch 42/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0035 - mae: 0.0206 - val_loss: 0.0036 - val_mae: 0.0184 - learning_rate: 1.0000e-04
Epoch 43/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0031 - mae: 0.0190 - val_loss: 0.0033 - val_mae: 0.0170 - learning_rate: 1.0000e-04
Epoch 44/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0032 - mae: 0.0200 - val_loss: 0.0032 - val_mae: 0.0163 - learning_rate: 1.0000e-04
Epoch 45/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0031 - mae: 0.0195 - val_loss: 0.0032 - val_mae: 0.0167 - learning_rate: 1.0000e-04
Epoch 46/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0029 - mae: 0.0192 - val_loss: 0.0035 - val_mae: 0.0196 - learning_rate: 1.0000e-04
Epoch 47/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0027 - mae: 0.0179 - val_loss: 0.0030 - val_mae: 0.0167 - learning_rate: 1.0000e-04
Epoch 48/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0028 - mae: 0.0192 - val_loss: 0.0027 - val_mae: 0.0160 - learning_rate: 1.0000e-04
Epoch 49/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0028 - mae: 0.0187 - val_loss: 0.0028 - val_mae: 0.0156 - learning_rate: 1.0000e-04
Epoch 50/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0025 - mae: 0.0170 - val_loss: 0.0025 - val_mae: 0.0143 - learning_rate: 1.0000e-04
Epoch 51/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0025 - mae: 0.0176 - val_loss: 0.0027 - val_mae: 0.0154 - learning_rate: 1.0000e-04
Epoch 52/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0027 - mae: 0.0188 - val_loss: 0.0027 - val_mae: 0.0163 - learning_rate: 1.0000e-04
Epoch 53/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0023 - mae: 0.0171 - val_loss: 0.0036 - val_mae: 0.0222 - learning_rate: 1.0000e-04
Epoch 54/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0024 - mae: 0.0175 - val_loss: 0.0026 - val_mae: 0.0174 - learning_rate: 1.0000e-04
Epoch 55/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0022 - mae: 0.0176 - val_loss: 0.0031 - val_mae: 0.0196 - learning_rate: 1.0000e-04
Epoch 56/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0024 - mae: 0.0183 - val_loss: 0.0033 - val_mae: 0.0233 - learning_rate: 1.0000e-04
Epoch 57/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0021 - mae: 0.0168 - val_loss: 0.0024 - val_mae: 0.0155 - learning_rate: 1.0000e-04
Epoch 58/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0021 - mae: 0.0171 - val_loss: 0.0028 - val_mae: 0.0186 - learning_rate: 1.0000e-04
Epoch 59/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0020 - mae: 0.0168 - val_loss: 0.0030 - val_mae: 0.0201 - learning_rate: 1.0000e-04
Epoch 60/60
145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0020 - mae: 0.0167 - val_loss: 0.0026 - val_mae: 0.0179 - learning_rate: 1.0000e-04
Restoring model weights from the end of the best epoch: 57.

Đánh giá mô hình¶

In [80]:
# Vẽ val_loss để đánh giá overfitting
plt.figure(figsize=(12, 8))

plt.subplot(2, 1, 1)
plt.plot(history_rnn.history['loss'], label='Training Loss', linewidth=2)
plt.plot(history_rnn.history['val_loss'], label='Validation Loss', linewidth=2)
plt.title('RNN Model Loss - Training vs Validation')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# Tìm epoch có val_loss thấp nhất
best_epoch = np.argmin(history_rnn.history['val_loss']) + 1
best_val_loss = min(history_rnn.history['val_loss'])
print(f"Epoch tốt nhất: {best_epoch} với val_loss: {best_val_loss:.6f}")
No description has been provided for this image
Epoch tốt nhất: 57 với val_loss: 0.002361

Dự đoán và trực quan hóa¶

In [81]:
# Dự đoán 30, 60, 90 ngày tiếp theo
last_data_scaled = scaled_test_input[-time_step:]

forecasted_prices_30 = forecast_multivariate_prices(model_rnn, last_data_scaled, time_step, 30, scaler_target)
forecasted_prices_60 = forecast_multivariate_prices(model_rnn, last_data_scaled, time_step, 60, scaler_target)
forecasted_prices_90 = forecast_multivariate_prices(model_rnn, last_data_scaled, time_step, 90, scaler_target)

# Dự đoán giá trên tập kiểm tra
test_predict_scaled = model_rnn.predict(X_test)
test_predict_rnn = scaler_target.inverse_transform(test_predict_scaled)

# Tạo DataFrame cho các dự đoán
forecast_dates_30 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=30, freq='D')
forecast_dates_60 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=60, freq='D')
forecast_dates_90 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=90, freq='D')

forecast_df_30 = pd.DataFrame(forecasted_prices_30, index=forecast_dates_30, columns=['Price'])
forecast_df_60 = pd.DataFrame(forecasted_prices_60, index=forecast_dates_60, columns=['Price'])
forecast_df_90 = pd.DataFrame(forecasted_prices_90, index=forecast_dates_90, columns=['Price'])

# Trực quan hóa kết quả
plt.figure(figsize=(16, 10))

# Vẽ giá thực tế
plt.plot(data.index, data['Price'], label='Giá thực tế', color='blue', linewidth=2, alpha=0.8)

# Vẽ dự đoán trên tập test
plt.plot(test_data.index[time_step:], test_predict_rnn,
         label='Dự đoán trên tập test', color='orange', linewidth=2, alpha=0.8)

# Vẽ các dự đoán tương lai
plt.plot(forecast_df_30.index, forecast_df_30['Price'],
         label='Dự đoán 30 ngày tiếp theo', color='red', linestyle='--', linewidth=2, alpha=0.7)

plt.plot(forecast_df_60.index, forecast_df_60['Price'],
         label='Dự đoán 60 ngày tiếp theo', color='green', linestyle='--', linewidth=2, alpha=0.5)

plt.plot(forecast_df_90.index, forecast_df_90['Price'],
         label='Dự đoán 90 ngày tiếp theo', color='purple', linestyle='--', linewidth=2, alpha=0.3)

# Thêm đường thẳng đứng để phân biệt vùng dữ liệu
plt.axvline(x=data.index[-1], color='black', linestyle=':', alpha=0.7,
            label='Ngày cuối cùng của dữ liệu thực tế')

plt.title(f'Dự đoán giá XRP bằng RNN (Time Step = {time_step})\nDự đoán 30, 60, 90 ngày tiếp theo',
          fontsize=16, fontweight='bold')
plt.xlabel('Ngày', fontsize=12)
plt.ylabel('Giá (USD)', fontsize=12)
plt.legend(fontsize=10, loc='upper left')
plt.grid(True, alpha=0.3)

plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
31/31 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step
No description has been provided for this image
In [82]:
# Đánh giá mô hình
# Lấy giá trị thực tế trên tập test
y_test_actual = test_data['Price'].values[time_step:]

# Tính toán các metrics
mape = mean_absolute_percentage_error(y_test_actual, test_predict_rnn.flatten())
mse = mean_squared_error(y_test_actual, test_predict_rnn.flatten())
rmse = np.sqrt(mse)

print(f'Kết quả đánh giá mô hình RNN (Time Step = {time_step}):')
print(f'MAPE: {mape:.2f}%')
print(f'MSE: {mse:.2f}')
print(f'RMSE: {rmse:.2f}')
print(f'Số epochs huấn luyện: {len(history_rnn.history["loss"])}')

# Hiển thị thông tin dự đoán 30 ngày
print(f'\nDự đoán giá XRP 30 ngày tiếp theo:')
print(f'Giá cao nhất: ${forecasted_prices_30.max():.2f}')
print(f'Giá thấp nhất: ${forecasted_prices_30.min():.2f}')
print(f'Giá trung bình: ${forecasted_prices_30.mean():.2f}')
Kết quả đánh giá mô hình RNN (Time Step = 50):
MAPE: 0.04%
MSE: 0.01
RMSE: 0.10
Số epochs huấn luyện: 60

Dự đoán giá XRP 30 ngày tiếp theo:
Giá cao nhất: $1.99
Giá thấp nhất: $1.38
Giá trung bình: $1.57

Chia 8:2¶

Chuẩn hóa dữ liệu 8:2¶

In [83]:
# Chuẩn hóa dữ liệu
# Lấy 3 cột Price, Open, Vol. để làm đầu vào và Price làm đầu ra
input_features = data[['Price', 'Open', 'Vol.']].values
target_feature = data[['Price']].values

# Áp dụng MinMaxScaler cho input features
scaler_input = MinMaxScaler(feature_range=(0, 1))
scaled_input = scaler_input.fit_transform(input_features)

# Áp dụng MinMaxScaler cho target feature
scaler_target = MinMaxScaler(feature_range=(0, 1))
scaled_target = scaler_target.fit_transform(target_feature)
In [84]:
# Chia dữ liệu train/test theo tỷ lệ 8:2
train_size_82 = int(len(data) * 0.8)
train_data_82 = data.iloc[0:train_size_82,:]
test_data_82 = data.iloc[train_size_82:len(data),:]

# Chia dữ liệu đã chuẩn hóa
scaled_train_input_82 = scaled_input[0:train_size_82,:]
scaled_test_input_82 = scaled_input[train_size_82:,:]
scaled_train_target_82 = scaled_target[0:train_size_82,:]
scaled_test_target_82 = scaled_target[train_size_82:,:]

print(f"Kích thước tập train 8:2: {len(train_data_82)}")
print(f"Kích thước tập test 8:2: {len(test_data_82)}")
Kích thước tập train 8:2: 2695
Kích thước tập test 8:2: 674

Xây dựng mô hình RNN¶

In [85]:
def build_rnn_model_with_regularization(time_step: int, num_features: int) -> Sequential:
    """
    Xây dựng mô hình RNN với regularization

    Args:
        time_step: Số time steps để nhìn về quá khứ
        num_features: Số features đầu vào (Price, Open, Vol = 3)

    Returns:
        Sequential model
    """
    model = Sequential()

    # SimpleRNN layer với regularization
    model.add(SimpleRNN(
        units=50,                       # Số neurons
        input_shape=(time_step, num_features),  # (50, 3)
        kernel_regularizer=regularizers.l2(0.001),    # L2 regularization
        return_sequences=False           # Chỉ cần output cuối cùng
    ))

    # Dropout để tránh overfitting
    model.add(Dropout(0.3))

    # Dense layer ẩn
    model.add(Dense(32, activation='relu'))
    model.add(Dropout(0.2))

    # Output layer: dự đoán 1 giá trị (Price)
    model.add(Dense(1))

    # Optimizer với learning rate nhỏ
    optimizer = Adam(learning_rate=1e-4)
    model.compile(optimizer=optimizer, loss='mean_squared_error', metrics=['mae'])

    return model
In [86]:
# Hàm tạo dữ liệu time series với multiple features
def create_multivariate_time_series_data(input_data: np.ndarray, target_data: np.ndarray, time_step: int) -> Tuple[np.ndarray, np.ndarray]:
    X_data, y_data = [], []
    for i in range(len(input_data) - time_step):
        X_data.append(input_data[i:(i + time_step), :])  # Lấy tất cả features
        y_data.append(target_data[i + time_step, 0])     # Chỉ lấy Price
    return np.array(X_data), np.array(y_data)
In [87]:
# Hàm dự đoán với multiple features
def forecast_multivariate_prices(model: Sequential, input_data: np.ndarray, time_step: int,
                                forecast_days: int, scaler_target: MinMaxScaler) -> np.ndarray:
    temp_input = input_data[-time_step:].reshape(1, time_step, input_data.shape[1])
    lst_output = []

    for _ in range(forecast_days):
        predicted_price = model.predict(temp_input, verbose=0)
        lst_output.append(predicted_price[0].tolist())

        # Tạo input mới cho prediction tiếp theo
        # Giả sử các features khác không đổi, chỉ cập nhật Price
        new_row = temp_input[0, -1, :].copy()
        new_row[0] = predicted_price[0, 0]  # Cập nhật Price prediction

        temp_input = np.append(temp_input[:, 1:, :],
                              new_row.reshape(1, 1, input_data.shape[1]), axis=1)

    # Chuyển đổi lst_output thành numpy array và inverse transform
    lst_output = np.array(lst_output).reshape(-1, 1)
    return scaler_target.inverse_transform(lst_output)

Huấn luyện mô hình 8:2¶

In [88]:
# Tạo dữ liệu train và test với time_step = 50 cho split 8:2
X_train_82, y_train_82 = create_multivariate_time_series_data(scaled_train_input_82, scaled_train_target_82, time_step)
X_test_82, y_test_82 = create_multivariate_time_series_data(scaled_test_input_82, scaled_test_target_82, time_step)

print(f"X_train_82 shape: {X_train_82.shape}")
print(f"y_train_82 shape: {y_train_82.shape}")
print(f"X_test_82 shape: {X_test_82.shape}")
print(f"y_test_82 shape: {y_test_82.shape}")

# Xây dựng mô hình RNN cho split 8:2
model_rnn_82 = build_rnn_model_with_regularization(time_step, 3)  # 3 features: Price, Open, Vol

# Callbacks để tối ưu hóa
early_stop_82 = EarlyStopping(monitor='val_loss', patience=15, restore_best_weights=True, verbose=1)
reduce_lr_82 = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=10, min_lr=1e-7, verbose=1)

# Huấn luyện mô hình 8:2
history_rnn_82 = model_rnn_82.fit(
    X_train_82, y_train_82,
    epochs=60,
    batch_size=16,
    validation_data=(X_test_82, y_test_82),
    callbacks=[early_stop_82, reduce_lr_82],
    verbose=1
)
X_train_82 shape: (2645, 50, 3)
y_train_82 shape: (2645,)
X_test_82 shape: (624, 50, 3)
y_test_82 shape: (624,)
Epoch 1/60
c:\Users\Hii\AppData\Local\Programs\Python\Python311\Lib\site-packages\keras\src\layers\rnn\rnn.py:199: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead.
  super().__init__(**kwargs)
166/166 ━━━━━━━━━━━━━━━━━━━━ 2s 6ms/step - loss: 0.0412 - mae: 0.1294 - val_loss: 0.0214 - val_mae: 0.0753 - learning_rate: 1.0000e-04
Epoch 2/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0170 - mae: 0.0689 - val_loss: 0.0112 - val_mae: 0.0455 - learning_rate: 1.0000e-04
Epoch 3/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0127 - mae: 0.0545 - val_loss: 0.0090 - val_mae: 0.0336 - learning_rate: 1.0000e-04
Epoch 4/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0117 - mae: 0.0485 - val_loss: 0.0073 - val_mae: 0.0237 - learning_rate: 1.0000e-04
Epoch 5/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0108 - mae: 0.0464 - val_loss: 0.0071 - val_mae: 0.0236 - learning_rate: 1.0000e-04
Epoch 6/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0096 - mae: 0.0402 - val_loss: 0.0087 - val_mae: 0.0442 - learning_rate: 1.0000e-04
Epoch 7/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0092 - mae: 0.0381 - val_loss: 0.0085 - val_mae: 0.0400 - learning_rate: 1.0000e-04
Epoch 8/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0089 - mae: 0.0359 - val_loss: 0.0075 - val_mae: 0.0311 - learning_rate: 1.0000e-04
Epoch 9/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0079 - mae: 0.0334 - val_loss: 0.0071 - val_mae: 0.0280 - learning_rate: 1.0000e-04
Epoch 10/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0079 - mae: 0.0325 - val_loss: 0.0073 - val_mae: 0.0332 - learning_rate: 1.0000e-04
Epoch 11/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0079 - mae: 0.0317 - val_loss: 0.0067 - val_mae: 0.0249 - learning_rate: 1.0000e-04
Epoch 12/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0075 - mae: 0.0316 - val_loss: 0.0065 - val_mae: 0.0249 - learning_rate: 1.0000e-04
Epoch 13/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0069 - mae: 0.0290 - val_loss: 0.0070 - val_mae: 0.0316 - learning_rate: 1.0000e-04
Epoch 14/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0071 - mae: 0.0293 - val_loss: 0.0065 - val_mae: 0.0278 - learning_rate: 1.0000e-04
Epoch 15/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0066 - mae: 0.0268 - val_loss: 0.0079 - val_mae: 0.0394 - learning_rate: 1.0000e-04
Epoch 16/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0064 - mae: 0.0270 - val_loss: 0.0064 - val_mae: 0.0279 - learning_rate: 1.0000e-04
Epoch 17/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0068 - mae: 0.0270 - val_loss: 0.0063 - val_mae: 0.0293 - learning_rate: 1.0000e-04
Epoch 18/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0059 - mae: 0.0244 - val_loss: 0.0059 - val_mae: 0.0234 - learning_rate: 1.0000e-04
Epoch 19/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0060 - mae: 0.0252 - val_loss: 0.0061 - val_mae: 0.0299 - learning_rate: 1.0000e-04
Epoch 20/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0060 - mae: 0.0265 - val_loss: 0.0064 - val_mae: 0.0319 - learning_rate: 1.0000e-04
Epoch 21/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0055 - mae: 0.0230 - val_loss: 0.0062 - val_mae: 0.0315 - learning_rate: 1.0000e-04
Epoch 22/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0054 - mae: 0.0235 - val_loss: 0.0052 - val_mae: 0.0222 - learning_rate: 1.0000e-04
Epoch 23/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0052 - mae: 0.0232 - val_loss: 0.0052 - val_mae: 0.0234 - learning_rate: 1.0000e-04
Epoch 24/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0054 - mae: 0.0231 - val_loss: 0.0055 - val_mae: 0.0308 - learning_rate: 1.0000e-04
Epoch 25/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0051 - mae: 0.0219 - val_loss: 0.0066 - val_mae: 0.0373 - learning_rate: 1.0000e-04
Epoch 26/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0048 - mae: 0.0223 - val_loss: 0.0051 - val_mae: 0.0219 - learning_rate: 1.0000e-04
Epoch 27/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0047 - mae: 0.0209 - val_loss: 0.0056 - val_mae: 0.0329 - learning_rate: 1.0000e-04
Epoch 28/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0043 - mae: 0.0203 - val_loss: 0.0055 - val_mae: 0.0304 - learning_rate: 1.0000e-04
Epoch 29/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0043 - mae: 0.0210 - val_loss: 0.0046 - val_mae: 0.0201 - learning_rate: 1.0000e-04
Epoch 30/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0042 - mae: 0.0202 - val_loss: 0.0046 - val_mae: 0.0211 - learning_rate: 1.0000e-04
Epoch 31/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0043 - mae: 0.0219 - val_loss: 0.0053 - val_mae: 0.0292 - learning_rate: 1.0000e-04
Epoch 32/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0040 - mae: 0.0200 - val_loss: 0.0069 - val_mae: 0.0417 - learning_rate: 1.0000e-04
Epoch 33/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0040 - mae: 0.0206 - val_loss: 0.0060 - val_mae: 0.0333 - learning_rate: 1.0000e-04
Epoch 34/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0038 - mae: 0.0196 - val_loss: 0.0040 - val_mae: 0.0187 - learning_rate: 1.0000e-04
Epoch 35/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0038 - mae: 0.0202 - val_loss: 0.0045 - val_mae: 0.0240 - learning_rate: 1.0000e-04
Epoch 36/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0038 - mae: 0.0193 - val_loss: 0.0051 - val_mae: 0.0313 - learning_rate: 1.0000e-04
Epoch 37/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0035 - mae: 0.0195 - val_loss: 0.0040 - val_mae: 0.0219 - learning_rate: 1.0000e-04
Epoch 38/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0033 - mae: 0.0183 - val_loss: 0.0062 - val_mae: 0.0370 - learning_rate: 1.0000e-04
Epoch 39/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0032 - mae: 0.0188 - val_loss: 0.0036 - val_mae: 0.0187 - learning_rate: 1.0000e-04
Epoch 40/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0033 - mae: 0.0183 - val_loss: 0.0047 - val_mae: 0.0313 - learning_rate: 1.0000e-04
Epoch 41/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0031 - mae: 0.0187 - val_loss: 0.0051 - val_mae: 0.0337 - learning_rate: 1.0000e-04
Epoch 42/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0030 - mae: 0.0180 - val_loss: 0.0044 - val_mae: 0.0285 - learning_rate: 1.0000e-04
Epoch 43/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0032 - mae: 0.0193 - val_loss: 0.0041 - val_mae: 0.0265 - learning_rate: 1.0000e-04
Epoch 44/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0029 - mae: 0.0183 - val_loss: 0.0038 - val_mae: 0.0236 - learning_rate: 1.0000e-04
Epoch 45/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0026 - mae: 0.0167 - val_loss: 0.0032 - val_mae: 0.0197 - learning_rate: 1.0000e-04
Epoch 46/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0027 - mae: 0.0172 - val_loss: 0.0043 - val_mae: 0.0274 - learning_rate: 1.0000e-04
Epoch 47/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0026 - mae: 0.0181 - val_loss: 0.0076 - val_mae: 0.0502 - learning_rate: 1.0000e-04
Epoch 48/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0026 - mae: 0.0183 - val_loss: 0.0048 - val_mae: 0.0319 - learning_rate: 1.0000e-04
Epoch 49/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0025 - mae: 0.0173 - val_loss: 0.0026 - val_mae: 0.0156 - learning_rate: 1.0000e-04
Epoch 50/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0024 - mae: 0.0175 - val_loss: 0.0051 - val_mae: 0.0341 - learning_rate: 1.0000e-04
Epoch 51/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0023 - mae: 0.0174 - val_loss: 0.0035 - val_mae: 0.0244 - learning_rate: 1.0000e-04
Epoch 52/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0024 - mae: 0.0174 - val_loss: 0.0038 - val_mae: 0.0288 - learning_rate: 1.0000e-04
Epoch 53/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0022 - mae: 0.0167 - val_loss: 0.0039 - val_mae: 0.0284 - learning_rate: 1.0000e-04
Epoch 54/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0023 - mae: 0.0176 - val_loss: 0.0030 - val_mae: 0.0220 - learning_rate: 1.0000e-04
Epoch 55/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0023 - mae: 0.0176 - val_loss: 0.0029 - val_mae: 0.0220 - learning_rate: 1.0000e-04
Epoch 56/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0021 - mae: 0.0165 - val_loss: 0.0029 - val_mae: 0.0219 - learning_rate: 1.0000e-04
Epoch 57/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0020 - mae: 0.0163 - val_loss: 0.0021 - val_mae: 0.0160 - learning_rate: 1.0000e-04
Epoch 58/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0020 - mae: 0.0169 - val_loss: 0.0039 - val_mae: 0.0291 - learning_rate: 1.0000e-04
Epoch 59/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0019 - mae: 0.0165 - val_loss: 0.0034 - val_mae: 0.0264 - learning_rate: 1.0000e-04
Epoch 60/60
166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0019 - mae: 0.0162 - val_loss: 0.0023 - val_mae: 0.0180 - learning_rate: 1.0000e-04
Restoring model weights from the end of the best epoch: 57.

Đánh giá mô hình 8:2¶

In [89]:
# Vẽ val_loss để đánh giá overfitting cho split 8:2
plt.figure(figsize=(12, 8))

plt.subplot(2, 1, 1)
plt.plot(history_rnn_82.history['loss'], label='Training Loss', linewidth=2)
plt.plot(history_rnn_82.history['val_loss'], label='Validation Loss', linewidth=2)
plt.title('RNN Model Loss 8:2 Split - Training vs Validation')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# Tìm epoch có val_loss thấp nhất cho split 8:2
best_epoch_82 = np.argmin(history_rnn_82.history['val_loss']) + 1
best_val_loss_82 = min(history_rnn_82.history['val_loss'])
print(f"Epoch tốt nhất (8:2): {best_epoch_82} với val_loss: {best_val_loss_82:.6f}")
No description has been provided for this image
Epoch tốt nhất (8:2): 57 với val_loss: 0.002149

Dự đoán và trực quan hóa 8:2¶

In [90]:
# Dự đoán 30, 60, 90 ngày tiếp theo cho split 8:2
last_data_scaled_82 = scaled_test_input_82[-time_step:]

forecasted_prices_30_82 = forecast_multivariate_prices(model_rnn_82, last_data_scaled_82, time_step, 30, scaler_target)
forecasted_prices_60_82 = forecast_multivariate_prices(model_rnn_82, last_data_scaled_82, time_step, 60, scaler_target)
forecasted_prices_90_82 = forecast_multivariate_prices(model_rnn_82, last_data_scaled_82, time_step, 90, scaler_target)

# Dự đoán giá trên tập kiểm tra cho split 8:2
test_predict_scaled_82 = model_rnn_82.predict(X_test_82)
test_predict_rnn_82 = scaler_target.inverse_transform(test_predict_scaled_82)

# Tạo DataFrame cho các dự đoán 8:2
forecast_dates_30_82 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=30, freq='D')
forecast_dates_60_82 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=60, freq='D')
forecast_dates_90_82 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=90, freq='D')

forecast_df_30_82 = pd.DataFrame(forecasted_prices_30_82, index=forecast_dates_30_82, columns=['Price'])
forecast_df_60_82 = pd.DataFrame(forecasted_prices_60_82, index=forecast_dates_60_82, columns=['Price'])
forecast_df_90_82 = pd.DataFrame(forecasted_prices_90_82, index=forecast_dates_90_82, columns=['Price'])

# Trực quan hóa kết quả cho split 8:2
plt.figure(figsize=(16, 10))

# Vẽ giá thực tế
plt.plot(data.index, data['Price'], label='Giá thực tế', color='blue', linewidth=2, alpha=0.8)

# Vẽ dự đoán trên tập test 8:2
plt.plot(test_data_82.index[time_step:], test_predict_rnn_82,
         label='Dự đoán trên tập test (8:2)', color='orange', linewidth=2, alpha=0.8)

# Vẽ các dự đoán tương lai 8:2
plt.plot(forecast_df_30_82.index, forecast_df_30_82['Price'],
         label='Dự đoán 30 ngày tiếp theo (8:2)', color='red', linestyle='--', linewidth=2, alpha=0.7)

plt.plot(forecast_df_60_82.index, forecast_df_60_82['Price'],
         label='Dự đoán 60 ngày tiếp theo (8:2)', color='green', linestyle='--', linewidth=2, alpha=0.5)

plt.plot(forecast_df_90_82.index, forecast_df_90_82['Price'],
         label='Dự đoán 90 ngày tiếp theo (8:2)', color='purple', linestyle='--', linewidth=2, alpha=0.3)

# Thêm đường thẳng đứng để phân biệt vùng dữ liệu
plt.axvline(x=data.index[-1], color='black', linestyle=':', alpha=0.7,
            label='Ngày cuối cùng của dữ liệu thực tế')

plt.title(f'Dự đoán giá XRP bằng RNN (8:2 Split, Time Step = {time_step})\nDự đoán 30, 60, 90 ngày tiếp theo',
          fontsize=16, fontweight='bold')
plt.xlabel('Ngày', fontsize=12)
plt.ylabel('Giá (USD)', fontsize=12)
plt.legend(fontsize=10, loc='upper left')
plt.grid(True, alpha=0.3)

plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
20/20 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step  
No description has been provided for this image
In [91]:
# Đánh giá mô hình 8:2
# Lấy giá trị thực tế trên tập test 8:2
y_test_actual_82 = test_data_82['Price'].values[time_step:]

# Tính toán các metrics cho split 8:2
mape_82 = mean_absolute_percentage_error(y_test_actual_82, test_predict_rnn_82.flatten())
mse_82 = mean_squared_error(y_test_actual_82, test_predict_rnn_82.flatten())
rmse_82 = np.sqrt(mse_82)

print(f'Kết quả đánh giá mô hình RNN 8:2 Split (Time Step = {time_step}):')
print(f'MAPE: {mape_82:.2f}%')
print(f'MSE: {mse_82:.2f}')
print(f'RMSE: {rmse_82:.2f}')
print(f'Số epochs huấn luyện: {len(history_rnn_82.history["loss"])}')

# Hiển thị thông tin dự đoán 30 ngày cho 8:2
print(f'\nDự đoán giá XRP 30 ngày tiếp theo (8:2):')
print(f'Giá cao nhất: ${forecasted_prices_30_82.max():.2f}')
print(f'Giá thấp nhất: ${forecasted_prices_30_82.min():.2f}')
print(f'Giá trung bình: ${forecasted_prices_30_82.mean():.2f}')
Kết quả đánh giá mô hình RNN 8:2 Split (Time Step = 50):
MAPE: 0.04%
MSE: 0.01
RMSE: 0.10
Số epochs huấn luyện: 60

Dự đoán giá XRP 30 ngày tiếp theo (8:2):
Giá cao nhất: $2.07
Giá thấp nhất: $1.98
Giá trung bình: $2.02

Chia 9:1¶

Chuẩn hóa dữ liệu 9:1¶

In [92]:
# Chuẩn hóa dữ liệu
# Lấy 3 cột Price, Open, Vol. để làm đầu vào và Price làm đầu ra
input_features = data[['Price', 'Open', 'Vol.']].values
target_feature = data[['Price']].values

# Áp dụng MinMaxScaler cho input features
scaler_input = MinMaxScaler(feature_range=(0, 1))
scaled_input = scaler_input.fit_transform(input_features)

# Áp dụng MinMaxScaler cho target feature
scaler_target = MinMaxScaler(feature_range=(0, 1))
scaled_target = scaler_target.fit_transform(target_feature)
In [93]:
# Chia dữ liệu train/test theo tỷ lệ 9:1
train_size_91 = int(len(data) * 0.9)
train_data_91 = data.iloc[0:train_size_91,:]
test_data_91 = data.iloc[train_size_91:len(data),:]

# Chia dữ liệu đã chuẩn hóa
scaled_train_input_91 = scaled_input[0:train_size_91,:]
scaled_test_input_91 = scaled_input[train_size_91:,:]
scaled_train_target_91 = scaled_target[0:train_size_91,:]
scaled_test_target_91 = scaled_target[train_size_91:,:]

print(f"Kích thước tập train 9:1: {len(train_data_91)}")
print(f"Kích thước tập test 9:1: {len(test_data_91)}")
Kích thước tập train 9:1: 3032
Kích thước tập test 9:1: 337

Xây dựng mô hình RNN¶

In [94]:
def build_rnn_model_with_regularization(time_step: int, num_features: int) -> Sequential:
    """
    Xây dựng mô hình RNN với regularization

    Args:
        time_step: Số time steps để nhìn về quá khứ
        num_features: Số features đầu vào (Price, Open, Vol = 3)

    Returns:
        Sequential model
    """
    model = Sequential()

    # SimpleRNN layer với regularization
    model.add(SimpleRNN(
        units=50,                       # Số neurons
        input_shape=(time_step, num_features),  # (50, 3)
        kernel_regularizer=regularizers.l2(0.001),    # L2 regularization
        return_sequences=False           # Chỉ cần output cuối cùng
    ))

    # Dropout để tránh overfitting
    model.add(Dropout(0.3))

    # Dense layer ẩn
    model.add(Dense(32, activation='relu'))
    model.add(Dropout(0.2))

    # Output layer: dự đoán 1 giá trị (Price)
    model.add(Dense(1))

    # Optimizer với learning rate nhỏ
    optimizer = Adam(learning_rate=1e-4)
    model.compile(optimizer=optimizer, loss='mean_squared_error', metrics=['mae'])

    return model
In [95]:
# Hàm tạo dữ liệu time series với multiple features
def create_multivariate_time_series_data(input_data: np.ndarray, target_data: np.ndarray, time_step: int) -> Tuple[np.ndarray, np.ndarray]:
    X_data, y_data = [], []
    for i in range(len(input_data) - time_step):
        X_data.append(input_data[i:(i + time_step), :])  # Lấy tất cả features
        y_data.append(target_data[i + time_step, 0])     # Chỉ lấy Price
    return np.array(X_data), np.array(y_data)
In [96]:
# Hàm dự đoán với multiple features
def forecast_multivariate_prices(model: Sequential, input_data: np.ndarray, time_step: int,
                                forecast_days: int, scaler_target: MinMaxScaler) -> np.ndarray:
    temp_input = input_data[-time_step:].reshape(1, time_step, input_data.shape[1])
    lst_output = []

    for _ in range(forecast_days):
        predicted_price = model.predict(temp_input, verbose=0)
        lst_output.append(predicted_price[0].tolist())

        # Tạo input mới cho prediction tiếp theo
        # Giả sử các features khác không đổi, chỉ cập nhật Price
        new_row = temp_input[0, -1, :].copy()
        new_row[0] = predicted_price[0, 0]  # Cập nhật Price prediction

        temp_input = np.append(temp_input[:, 1:, :],
                              new_row.reshape(1, 1, input_data.shape[1]), axis=1)

    # Chuyển đổi lst_output thành numpy array và inverse transform
    lst_output = np.array(lst_output).reshape(-1, 1)
    return scaler_target.inverse_transform(lst_output)

Huấn luyện mô hình 9:1¶

In [97]:
# Tạo dữ liệu train và test với time_step = 50 cho split 9:1
X_train_91, y_train_91 = create_multivariate_time_series_data(scaled_train_input_91, scaled_train_target_91, time_step)
X_test_91, y_test_91 = create_multivariate_time_series_data(scaled_test_input_91, scaled_test_target_91, time_step)

print(f"X_train_91 shape: {X_train_91.shape}")
print(f"y_train_91 shape: {y_train_91.shape}")
print(f"X_test_91 shape: {X_test_91.shape}")
print(f"y_test_91 shape: {y_test_91.shape}")

# Xây dựng mô hình RNN cho split 9:1
model_rnn_91 = build_rnn_model_with_regularization(time_step, 3)  # 3 features: Price, Open, Vol

# Callbacks để tối ưu hóa
early_stop_91 = EarlyStopping(monitor='val_loss', patience=15, restore_best_weights=True, verbose=1)
reduce_lr_91 = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=10, min_lr=1e-7, verbose=1)

# Huấn luyện mô hình 9:1
history_rnn_91 = model_rnn_91.fit(
    X_train_91, y_train_91,
    epochs=60,
    batch_size=16,
    validation_data=(X_test_91, y_test_91),
    callbacks=[early_stop_91, reduce_lr_91],
    verbose=1
)
X_train_91 shape: (2982, 50, 3)
y_train_91 shape: (2982,)
X_test_91 shape: (287, 50, 3)
y_test_91 shape: (287,)
Epoch 1/60
c:\Users\Hii\AppData\Local\Programs\Python\Python311\Lib\site-packages\keras\src\layers\rnn\rnn.py:199: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead.
  super().__init__(**kwargs)
187/187 ━━━━━━━━━━━━━━━━━━━━ 3s 6ms/step - loss: 0.0511 - mae: 0.1464 - val_loss: 0.0229 - val_mae: 0.1005 - learning_rate: 1.0000e-04
Epoch 2/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0158 - mae: 0.0662 - val_loss: 0.0190 - val_mae: 0.0880 - learning_rate: 1.0000e-04
Epoch 3/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0132 - mae: 0.0561 - val_loss: 0.0174 - val_mae: 0.0879 - learning_rate: 1.0000e-04
Epoch 4/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0117 - mae: 0.0494 - val_loss: 0.0113 - val_mae: 0.0561 - learning_rate: 1.0000e-04
Epoch 5/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0106 - mae: 0.0447 - val_loss: 0.0151 - val_mae: 0.0784 - learning_rate: 1.0000e-04
Epoch 6/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0091 - mae: 0.0391 - val_loss: 0.0214 - val_mae: 0.1064 - learning_rate: 1.0000e-04
Epoch 7/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0087 - mae: 0.0372 - val_loss: 0.0150 - val_mae: 0.0785 - learning_rate: 1.0000e-04
Epoch 8/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0083 - mae: 0.0356 - val_loss: 0.0101 - val_mae: 0.0513 - learning_rate: 1.0000e-04
Epoch 9/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0080 - mae: 0.0333 - val_loss: 0.0110 - val_mae: 0.0575 - learning_rate: 1.0000e-04
Epoch 10/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0082 - mae: 0.0340 - val_loss: 0.0101 - val_mae: 0.0545 - learning_rate: 1.0000e-04
Epoch 11/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0074 - mae: 0.0315 - val_loss: 0.0132 - val_mae: 0.0731 - learning_rate: 1.0000e-04
Epoch 12/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0072 - mae: 0.0287 - val_loss: 0.0120 - val_mae: 0.0674 - learning_rate: 1.0000e-04
Epoch 13/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0071 - mae: 0.0300 - val_loss: 0.0128 - val_mae: 0.0695 - learning_rate: 1.0000e-04
Epoch 14/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0066 - mae: 0.0277 - val_loss: 0.0133 - val_mae: 0.0729 - learning_rate: 1.0000e-04
Epoch 15/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0066 - mae: 0.0266 - val_loss: 0.0118 - val_mae: 0.0656 - learning_rate: 1.0000e-04
Epoch 16/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0060 - mae: 0.0248 - val_loss: 0.0127 - val_mae: 0.0734 - learning_rate: 1.0000e-04
Epoch 17/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0060 - mae: 0.0247 - val_loss: 0.0110 - val_mae: 0.0646 - learning_rate: 1.0000e-04
Epoch 18/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0060 - mae: 0.0251 - val_loss: 0.0084 - val_mae: 0.0478 - learning_rate: 1.0000e-04
Epoch 19/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0059 - mae: 0.0243 - val_loss: 0.0117 - val_mae: 0.0703 - learning_rate: 1.0000e-04
Epoch 20/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0055 - mae: 0.0236 - val_loss: 0.0113 - val_mae: 0.0669 - learning_rate: 1.0000e-04
Epoch 21/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0056 - mae: 0.0252 - val_loss: 0.0082 - val_mae: 0.0493 - learning_rate: 1.0000e-04
Epoch 22/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0053 - mae: 0.0237 - val_loss: 0.0070 - val_mae: 0.0415 - learning_rate: 1.0000e-04
Epoch 23/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0050 - mae: 0.0219 - val_loss: 0.0116 - val_mae: 0.0710 - learning_rate: 1.0000e-04
Epoch 24/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0048 - mae: 0.0223 - val_loss: 0.0081 - val_mae: 0.0514 - learning_rate: 1.0000e-04
Epoch 25/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 4ms/step - loss: 0.0048 - mae: 0.0207 - val_loss: 0.0081 - val_mae: 0.0517 - learning_rate: 1.0000e-04
Epoch 26/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 4ms/step - loss: 0.0047 - mae: 0.0209 - val_loss: 0.0086 - val_mae: 0.0539 - learning_rate: 1.0000e-04
Epoch 27/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0044 - mae: 0.0213 - val_loss: 0.0090 - val_mae: 0.0599 - learning_rate: 1.0000e-04
Epoch 28/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0041 - mae: 0.0193 - val_loss: 0.0090 - val_mae: 0.0588 - learning_rate: 1.0000e-04
Epoch 29/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0041 - mae: 0.0198 - val_loss: 0.0123 - val_mae: 0.0771 - learning_rate: 1.0000e-04
Epoch 30/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0039 - mae: 0.0193 - val_loss: 0.0061 - val_mae: 0.0388 - learning_rate: 1.0000e-04
Epoch 31/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0037 - mae: 0.0183 - val_loss: 0.0079 - val_mae: 0.0552 - learning_rate: 1.0000e-04
Epoch 32/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0036 - mae: 0.0184 - val_loss: 0.0073 - val_mae: 0.0510 - learning_rate: 1.0000e-04
Epoch 33/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0037 - mae: 0.0181 - val_loss: 0.0091 - val_mae: 0.0623 - learning_rate: 1.0000e-04
Epoch 34/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0034 - mae: 0.0184 - val_loss: 0.0107 - val_mae: 0.0704 - learning_rate: 1.0000e-04
Epoch 35/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0033 - mae: 0.0176 - val_loss: 0.0117 - val_mae: 0.0774 - learning_rate: 1.0000e-04
Epoch 36/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0034 - mae: 0.0183 - val_loss: 0.0077 - val_mae: 0.0554 - learning_rate: 1.0000e-04
Epoch 37/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0032 - mae: 0.0178 - val_loss: 0.0089 - val_mae: 0.0653 - learning_rate: 1.0000e-04
Epoch 38/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0029 - mae: 0.0174 - val_loss: 0.0086 - val_mae: 0.0624 - learning_rate: 1.0000e-04
Epoch 39/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0031 - mae: 0.0182 - val_loss: 0.0064 - val_mae: 0.0484 - learning_rate: 1.0000e-04
Epoch 40/60
180/187 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.0027 - mae: 0.0162
Epoch 40: ReduceLROnPlateau reducing learning rate to 4.999999873689376e-05.
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0027 - mae: 0.0162 - val_loss: 0.0063 - val_mae: 0.0474 - learning_rate: 1.0000e-04
Epoch 41/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 4ms/step - loss: 0.0028 - mae: 0.0174 - val_loss: 0.0049 - val_mae: 0.0374 - learning_rate: 5.0000e-05
Epoch 42/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0026 - mae: 0.0166 - val_loss: 0.0073 - val_mae: 0.0552 - learning_rate: 5.0000e-05
Epoch 43/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0025 - mae: 0.0158 - val_loss: 0.0059 - val_mae: 0.0459 - learning_rate: 5.0000e-05
Epoch 44/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0025 - mae: 0.0162 - val_loss: 0.0074 - val_mae: 0.0568 - learning_rate: 5.0000e-05
Epoch 45/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0024 - mae: 0.0158 - val_loss: 0.0048 - val_mae: 0.0379 - learning_rate: 5.0000e-05
Epoch 46/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0026 - mae: 0.0159 - val_loss: 0.0069 - val_mae: 0.0541 - learning_rate: 5.0000e-05
Epoch 47/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0026 - mae: 0.0166 - val_loss: 0.0083 - val_mae: 0.0623 - learning_rate: 5.0000e-05
Epoch 48/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0025 - mae: 0.0167 - val_loss: 0.0068 - val_mae: 0.0527 - learning_rate: 5.0000e-05
Epoch 49/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0025 - mae: 0.0163 - val_loss: 0.0055 - val_mae: 0.0451 - learning_rate: 5.0000e-05
Epoch 50/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0023 - mae: 0.0156 - val_loss: 0.0076 - val_mae: 0.0587 - learning_rate: 5.0000e-05
Epoch 51/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0023 - mae: 0.0156 - val_loss: 0.0073 - val_mae: 0.0579 - learning_rate: 5.0000e-05
Epoch 52/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0024 - mae: 0.0162 - val_loss: 0.0074 - val_mae: 0.0588 - learning_rate: 5.0000e-05
Epoch 53/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0024 - mae: 0.0160 - val_loss: 0.0069 - val_mae: 0.0552 - learning_rate: 5.0000e-05
Epoch 54/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0023 - mae: 0.0159 - val_loss: 0.0060 - val_mae: 0.0493 - learning_rate: 5.0000e-05
Epoch 55/60
182/187 ━━━━━━━━━━━━━━━━━━━━ 0s 6ms/step - loss: 0.0024 - mae: 0.0165
Epoch 55: ReduceLROnPlateau reducing learning rate to 2.499999936844688e-05.
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0024 - mae: 0.0164 - val_loss: 0.0066 - val_mae: 0.0538 - learning_rate: 5.0000e-05
Epoch 56/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0021 - mae: 0.0156 - val_loss: 0.0055 - val_mae: 0.0468 - learning_rate: 2.5000e-05
Epoch 57/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0020 - mae: 0.0152 - val_loss: 0.0059 - val_mae: 0.0491 - learning_rate: 2.5000e-05
Epoch 58/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0021 - mae: 0.0156 - val_loss: 0.0058 - val_mae: 0.0488 - learning_rate: 2.5000e-05
Epoch 59/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0021 - mae: 0.0158 - val_loss: 0.0068 - val_mae: 0.0559 - learning_rate: 2.5000e-05
Epoch 60/60
187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0020 - mae: 0.0150 - val_loss: 0.0086 - val_mae: 0.0657 - learning_rate: 2.5000e-05
Epoch 60: early stopping
Restoring model weights from the end of the best epoch: 45.

Đánh giá mô hình 9:1¶

In [98]:
# Vẽ val_loss để đánh giá overfitting cho split 9:1
plt.figure(figsize=(12, 8))

plt.subplot(2, 1, 1)
plt.plot(history_rnn_91.history['loss'], label='Training Loss', linewidth=2)
plt.plot(history_rnn_91.history['val_loss'], label='Validation Loss', linewidth=2)
plt.title('RNN Model Loss 9:1 Split - Training vs Validation')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# Tìm epoch có val_loss thấp nhất cho split 9:1
best_epoch_91 = np.argmin(history_rnn_91.history['val_loss']) + 1
best_val_loss_91 = min(history_rnn_91.history['val_loss'])
print(f"Epoch tốt nhất (9:1): {best_epoch_91} với val_loss: {best_val_loss_91:.6f}")
No description has been provided for this image
Epoch tốt nhất (9:1): 45 với val_loss: 0.004782

Dự đoán và trực quan hóa 9:1¶

In [99]:
# Dự đoán 30, 60, 90 ngày tiếp theo cho split 9:1
last_data_scaled_91 = scaled_test_input_91[-time_step:]

forecasted_prices_30_91 = forecast_multivariate_prices(model_rnn_91, last_data_scaled_91, time_step, 30, scaler_target)
forecasted_prices_60_91 = forecast_multivariate_prices(model_rnn_91, last_data_scaled_91, time_step, 60, scaler_target)
forecasted_prices_90_91 = forecast_multivariate_prices(model_rnn_91, last_data_scaled_91, time_step, 90, scaler_target)

# Dự đoán giá trên tập kiểm tra cho split 9:1
test_predict_scaled_91 = model_rnn_91.predict(X_test_91)
test_predict_rnn_91 = scaler_target.inverse_transform(test_predict_scaled_91)

# Tạo DataFrame cho các dự đoán 9:1
forecast_dates_30_91 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=30, freq='D')
forecast_dates_60_91 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=60, freq='D')
forecast_dates_90_91 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=90, freq='D')

forecast_df_30_91 = pd.DataFrame(forecasted_prices_30_91, index=forecast_dates_30_91, columns=['Price'])
forecast_df_60_91 = pd.DataFrame(forecasted_prices_60_91, index=forecast_dates_60_91, columns=['Price'])
forecast_df_90_91 = pd.DataFrame(forecasted_prices_90_91, index=forecast_dates_90_91, columns=['Price'])

# Trực quan hóa kết quả cho split 9:1
plt.figure(figsize=(16, 10))

# Vẽ giá thực tế
plt.plot(data.index, data['Price'], label='Giá thực tế', color='blue', linewidth=2, alpha=0.8)

# Vẽ dự đoán trên tập test 9:1
plt.plot(test_data_91.index[time_step:], test_predict_rnn_91,
         label='Dự đoán trên tập test (9:1)', color='orange', linewidth=2, alpha=0.8)

# Vẽ các dự đoán tương lai 9:1
plt.plot(forecast_df_30_91.index, forecast_df_30_91['Price'],
         label='Dự đoán 30 ngày tiếp theo (9:1)', color='red', linestyle='--', linewidth=2, alpha=0.7)

plt.plot(forecast_df_60_91.index, forecast_df_60_91['Price'],
         label='Dự đoán 60 ngày tiếp theo (9:1)', color='green', linestyle='--', linewidth=2, alpha=0.5)

plt.plot(forecast_df_90_91.index, forecast_df_90_91['Price'],
         label='Dự đoán 90 ngày tiếp theo (9:1)', color='purple', linestyle='--', linewidth=2, alpha=0.3)

# Thêm đường thẳng đứng để phân biệt vùng dữ liệu
plt.axvline(x=data.index[-1], color='black', linestyle=':', alpha=0.7,
            label='Ngày cuối cùng của dữ liệu thực tế')

plt.title(f'Dự đoán giá XRP bằng RNN (9:1 Split, Time Step = {time_step})\nDự đoán 30, 60, 90 ngày tiếp theo',
          fontsize=16, fontweight='bold')
plt.xlabel('Ngày', fontsize=12)
plt.ylabel('Giá (USD)', fontsize=12)
plt.legend(fontsize=10, loc='upper left')
plt.grid(True, alpha=0.3)

plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
9/9 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step  
No description has been provided for this image
In [100]:
# Đánh giá mô hình 9:1
# Lấy giá trị thực tế trên tập test 9:1
y_test_actual_91 = test_data_91['Price'].values[time_step:]

# Tính toán các metrics cho split 9:1
mape_91 = mean_absolute_percentage_error(y_test_actual_91, test_predict_rnn_91.flatten())
mse_91 = mean_squared_error(y_test_actual_91, test_predict_rnn_91.flatten())
rmse_91 = np.sqrt(mse_91)

print(f'Kết quả đánh giá mô hình RNN 9:1 Split (Time Step = {time_step}):')
print(f'MAPE: {mape_91:.2f}%')
print(f'MSE: {mse_91:.2f}')
print(f'RMSE: {rmse_91:.2f}')
print(f'Số epochs huấn luyện: {len(history_rnn_91.history["loss"])}')

# Hiển thị thông tin dự đoán 30 ngày cho 9:1
print(f'\nDự đoán giá XRP 30 ngày tiếp theo (9:1):')
print(f'Giá cao nhất: ${forecasted_prices_30_91.max():.2f}')
print(f'Giá thấp nhất: ${forecasted_prices_30_91.min():.2f}')
print(f'Giá trung bình: ${forecasted_prices_30_91.mean():.2f}')
Kết quả đánh giá mô hình RNN 9:1 Split (Time Step = 50):
MAPE: 0.06%
MSE: 0.03
RMSE: 0.18
Số epochs huấn luyện: 60

Dự đoán giá XRP 30 ngày tiếp theo (9:1):
Giá cao nhất: $2.02
Giá thấp nhất: $1.80
Giá trung bình: $1.84

So sánh 3 tỉ lệ¶

In [101]:
# So sánh chi tiết giữa 3 tỉ lệ chia dữ liệu
print("="*80)
print("SO SÁNH CHI TIẾT GIỮA 3 TỈ LỆ CHIA DỮ LIỆU")
print("="*80)

# Thu thập thông tin từ 3 splits
splits_info = {
    '7:3': {
        'train_size': len(train_data),
        'test_size': len(test_data),
        'mape': mape,
        'mse': mse,
        'rmse': rmse,
        'epochs': len(history_rnn.history['loss']),
        'best_val_loss': best_val_loss,
        'best_epoch': best_epoch,
        'final_train_loss': history_rnn.history['loss'][-1],
        'final_val_loss': history_rnn.history['val_loss'][-1]
    },
    '8:2': {
        'train_size': len(train_data_82),
        'test_size': len(test_data_82),
        'mape': mape_82,
        'mse': mse_82,
        'rmse': rmse_82,
        'epochs': len(history_rnn_82.history['loss']),
        'best_val_loss': best_val_loss_82,
        'best_epoch': best_epoch_82,
        'final_train_loss': history_rnn_82.history['loss'][-1],
        'final_val_loss': history_rnn_82.history['val_loss'][-1]
    },
    '9:1': {
        'train_size': len(train_data_91),
        'test_size': len(test_data_91),
        'mape': mape_91,
        'mse': mse_91,
        'rmse': rmse_91,
        'epochs': len(history_rnn_91.history['loss']),
        'best_val_loss': best_val_loss_91,
        'best_epoch': best_epoch_91,
        'final_train_loss': history_rnn_91.history['loss'][-1],
        'final_val_loss': history_rnn_91.history['val_loss'][-1]
    }
}

# In bảng so sánh
for split, info in splits_info.items():
    print(f"\n{split} Split:")
    print(f"  Kích thước train: {info['train_size']:,} mẫu")
    print(f"  Kích thước test: {info['test_size']:,} mẫu")
    print(f"  MAPE: {info['mape']:.2f}%")
    print(f"  MSE: {info['mse']:,.2f}")
    print(f"  RMSE: {info['rmse']:,.2f}")
    print(f"  Số epochs: {info['epochs']}")
    print(f"  Best val_loss: {info['best_val_loss']:.6f} (epoch {info['best_epoch']})")
    print(f"  Final train_loss: {info['final_train_loss']:.6f}")
    print(f"  Final val_loss: {info['final_val_loss']:.6f}")
    print(f"  Overfitting gap: {abs(info['final_val_loss'] - info['final_train_loss']):.6f}")
================================================================================
SO SÁNH CHI TIẾT GIỮA 3 TỈ LỆ CHIA DỮ LIỆU
================================================================================

7:3 Split:
  Kích thước train: 2,358 mẫu
  Kích thước test: 1,011 mẫu
  MAPE: 0.04%
  MSE: 0.01
  RMSE: 0.10
  Số epochs: 60
  Best val_loss: 0.002361 (epoch 57)
  Final train_loss: 0.001981
  Final val_loss: 0.002595
  Overfitting gap: 0.000614

8:2 Split:
  Kích thước train: 2,695 mẫu
  Kích thước test: 674 mẫu
  MAPE: 0.04%
  MSE: 0.01
  RMSE: 0.10
  Số epochs: 60
  Best val_loss: 0.002149 (epoch 57)
  Final train_loss: 0.001851
  Final val_loss: 0.002305
  Overfitting gap: 0.000453

9:1 Split:
  Kích thước train: 3,032 mẫu
  Kích thước test: 337 mẫu
  MAPE: 0.06%
  MSE: 0.03
  RMSE: 0.18
  Số epochs: 60
  Best val_loss: 0.004782 (epoch 45)
  Final train_loss: 0.002065
  Final val_loss: 0.008559
  Overfitting gap: 0.006494
In [102]:
# Vẽ biểu đồ so sánh các metrics
fig, axes = plt.subplots(2, 3, figsize=(18, 12))

splits = ['7:3', '8:2', '9:1']
colors = ['#1f77b4', '#ff7f0e', '#2ca02c']

# 1. So sánh MAPE
mape_values = [splits_info[split]['mape'] for split in splits]
axes[0, 0].bar(splits, mape_values, color=colors, alpha=0.7)
axes[0, 0].set_title('So sánh MAPE (%)', fontsize=14, fontweight='bold')
axes[0, 0].set_ylabel('MAPE (%)')
axes[0, 0].grid(True, alpha=0.3)
for i, v in enumerate(mape_values):
    axes[0, 0].text(i, v + 0.1, f'{v:.2f}%', ha='center', va='bottom', fontweight='bold')

# 2. So sánh RMSE
rmse_values = [splits_info[split]['rmse'] for split in splits]
axes[0, 1].bar(splits, rmse_values, color=colors, alpha=0.7)
axes[0, 1].set_title('So sánh RMSE (USD)', fontsize=14, fontweight='bold')
axes[0, 1].set_ylabel('RMSE (USD)')
axes[0, 1].grid(True, alpha=0.3)
for i, v in enumerate(rmse_values):
    axes[0, 1].text(i, v + 200, f'{v:,.0f}', ha='center', va='bottom', fontweight='bold')

# 3. So sánh Best Validation Loss
best_val_loss_values = [splits_info[split]['best_val_loss'] for split in splits]
axes[0, 2].bar(splits, best_val_loss_values, color=colors, alpha=0.7)
axes[0, 2].set_title('So sánh Best Validation Loss', fontsize=14, fontweight='bold')
axes[0, 2].set_ylabel('Best Val Loss')
axes[0, 2].grid(True, alpha=0.3)
for i, v in enumerate(best_val_loss_values):
    axes[0, 2].text(i, v + 0.0001, f'{v:.4f}', ha='center', va='bottom', fontweight='bold')

# 4. So sánh số epochs
epochs_values = [splits_info[split]['epochs'] for split in splits]
axes[1, 0].bar(splits, epochs_values, color=colors, alpha=0.7)
axes[1, 0].set_title('So sánh Số Epochs', fontsize=14, fontweight='bold')
axes[1, 0].set_ylabel('Số Epochs')
axes[1, 0].grid(True, alpha=0.3)
for i, v in enumerate(epochs_values):
    axes[1, 0].text(i, v + 0.5, f'{v}', ha='center', va='bottom', fontweight='bold')

# 5. So sánh Overfitting Gap
overfitting_gap = [abs(splits_info[split]['final_val_loss'] - splits_info[split]['final_train_loss']) 
                   for split in splits]
axes[1, 1].bar(splits, overfitting_gap, color=colors, alpha=0.7)
axes[1, 1].set_title('So sánh Overfitting Gap', fontsize=14, fontweight='bold')
axes[1, 1].set_ylabel('|Val Loss - Train Loss|')
axes[1, 1].grid(True, alpha=0.3)
for i, v in enumerate(overfitting_gap):
    axes[1, 1].text(i, v + 0.001, f'{v:.4f}', ha='center', va='bottom', fontweight='bold')

# 6. So sánh kích thước test set
test_sizes = [splits_info[split]['test_size'] for split in splits]
axes[1, 2].bar(splits, test_sizes, color=colors, alpha=0.7)
axes[1, 2].set_title('So sánh Kích thước Test Set', fontsize=14, fontweight='bold')
axes[1, 2].set_ylabel('Số mẫu test')
axes[1, 2].grid(True, alpha=0.3)
for i, v in enumerate(test_sizes):
    axes[1, 2].text(i, v + 20, f'{v:,}', ha='center', va='bottom', fontweight='bold')

plt.tight_layout()
plt.show()
C:\Users\Hii\AppData\Local\Temp\ipykernel_28116\2283599594.py:62: UserWarning: Tight layout not applied. The bottom and top margins cannot be made large enough to accommodate all Axes decorations.
  plt.tight_layout()
No description has been provided for this image
In [103]:
# Vẽ so sánh Training và Validation Loss curves
plt.figure(figsize=(18, 6))

# Subplot 1: 7:3 Split
plt.subplot(1, 3, 1)
plt.plot(history_rnn.history['loss'], label='Training Loss', linewidth=2, color='blue')
plt.plot(history_rnn.history['val_loss'], label='Validation Loss', linewidth=2, color='red')
plt.title('7:3 Split - Loss Curves', fontsize=14, fontweight='bold')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.axvline(x=best_epoch-1, color='green', linestyle='--', alpha=0.7, label=f'Best Epoch: {best_epoch}')

# Subplot 2: 8:2 Split
plt.subplot(1, 3, 2)
plt.plot(history_rnn_82.history['loss'], label='Training Loss', linewidth=2, color='blue')
plt.plot(history_rnn_82.history['val_loss'], label='Validation Loss', linewidth=2, color='red')
plt.title('8:2 Split - Loss Curves', fontsize=14, fontweight='bold')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.axvline(x=best_epoch_82-1, color='green', linestyle='--', alpha=0.7, label=f'Best Epoch: {best_epoch_82}')

# Subplot 3: 9:1 Split
plt.subplot(1, 3, 3)
plt.plot(history_rnn_91.history['loss'], label='Training Loss', linewidth=2, color='blue')
plt.plot(history_rnn_91.history['val_loss'], label='Validation Loss', linewidth=2, color='red')
plt.title('9:1 Split - Loss Curves', fontsize=14, fontweight='bold')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.axvline(x=best_epoch_91-1, color='green', linestyle='--', alpha=0.7, label=f'Best Epoch: {best_epoch_91}')

plt.tight_layout()
plt.show()
No description has been provided for this image
In [104]:
# Tạo DataFrame tổng hợp kết quả để dễ so sánh
import pandas as pd

comparison_df = pd.DataFrame({
    'Split': ['7:3', '8:2', '9:1'],
    'Train_Size': [splits_info[split]['train_size'] for split in splits],
    'Test_Size': [splits_info[split]['test_size'] for split in splits],
    'MAPE (%)': [splits_info[split]['mape'] for split in splits],
    'RMSE (USD)': [splits_info[split]['rmse'] for split in splits],
    'MSE': [splits_info[split]['mse'] for split in splits],
    'Best_Val_Loss': [splits_info[split]['best_val_loss'] for split in splits],
    'Best_Epoch': [splits_info[split]['best_epoch'] for split in splits],
    'Total_Epochs': [splits_info[split]['epochs'] for split in splits],
    'Overfitting_Gap': [abs(splits_info[split]['final_val_loss'] - splits_info[split]['final_train_loss']) 
                        for split in splits]
})

print("\nBẢNG TỔNG HỢP KẾT QUẢ:")
print("="*100)
print(comparison_df.to_string(index=False, float_format='%.4f'))
BẢNG TỔNG HỢP KẾT QUẢ:
====================================================================================================
Split  Train_Size  Test_Size  MAPE (%)  RMSE (USD)    MSE  Best_Val_Loss  Best_Epoch  Total_Epochs  Overfitting_Gap
  7:3        2358       1011    0.0423      0.1026 0.0105         0.0024          57            60           0.0006
  8:2        2695        674    0.0421      0.0995 0.0099         0.0021          57            60           0.0005
  9:1        3032        337    0.0614      0.1795 0.0322         0.0048          45            60           0.0065
In [105]:
# Phân tích và đưa ra khuyến nghị
print("\n" + "="*80)
print("PHÂN TÍCH VÀ KHUYẾN NGHỊ")
print("="*80)

# Tìm split tốt nhất cho từng metric
best_mape_split = splits[np.argmin([splits_info[split]['mape'] for split in splits])]
best_rmse_split = splits[np.argmin([splits_info[split]['rmse'] for split in splits])]
best_val_loss_split = splits[np.argmin([splits_info[split]['best_val_loss'] for split in splits])]
best_overfitting_split = splits[np.argmin([abs(splits_info[split]['final_val_loss'] - splits_info[split]['final_train_loss']) 
                                          for split in splits])]

print(f"\n1. PHÂN TÍCH THEO TỪNG TIÊU CHÍ:")
print(f"   • Tốt nhất theo MAPE: {best_mape_split} ({splits_info[best_mape_split]['mape']:.2f}%)")
print(f"   • Tốt nhất theo RMSE: {best_rmse_split} ({splits_info[best_rmse_split]['rmse']:,.2f} USD)")
print(f"   • Tốt nhất theo Val Loss: {best_val_loss_split} ({splits_info[best_val_loss_split]['best_val_loss']:.6f})")
print(f"   • Ít overfitting nhất: {best_overfitting_split} (gap: {abs(splits_info[best_overfitting_split]['final_val_loss'] - splits_info[best_overfitting_split]['final_train_loss']):.6f})")

# Tính điểm tổng hợp (rank-based scoring)
def calculate_rank_score(splits_info):
    scores = {}
    splits_list = list(splits_info.keys())
    
    # Rank cho MAPE (thấp hơn = tốt hơn)
    mape_rank = sorted(splits_list, key=lambda x: splits_info[x]['mape'])
    # Rank cho RMSE (thấp hơn = tốt hơn)
    rmse_rank = sorted(splits_list, key=lambda x: splits_info[x]['rmse'])
    # Rank cho Best Val Loss (thấp hơn = tốt hơn)
    val_loss_rank = sorted(splits_list, key=lambda x: splits_info[x]['best_val_loss'])
    # Rank cho Overfitting Gap (thấp hơn = tốt hơn)
    overfitting_rank = sorted(splits_list, key=lambda x: abs(splits_info[x]['final_val_loss'] - splits_info[x]['final_train_loss']))
    
    for split in splits_list:
        # Điểm rank (1 = tốt nhất, 3 = kém nhất)
        score = (mape_rank.index(split) + 1) * 0.3 + \
                (rmse_rank.index(split) + 1) * 0.3 + \
                (val_loss_rank.index(split) + 1) * 0.25 + \
                (overfitting_rank.index(split) + 1) * 0.15
        scores[split] = score
    
    return scores

# Tính điểm tổng hợp
scores = calculate_rank_score(splits_info)
best_overall_split = min(scores, key=scores.get)
print(f"\n2. ĐIỂM TỔNG HỢP (trọng số: MAPE=30%, RMSE=30%, Val_Loss=25%, Overfitting=15%):")
for split in splits_info.keys():
    print(f"   • {split}: {scores[split]:.2f} điểm")
    print(f"   • {split}: {scores[split]:.2f} điểm")

print(f"\n3. KẾT LUẬN VÀ KHUYẾN NGHỊ:")
print(f"   🏆 MÔ HÌNH TỐT NHẤT: Split {best_overall_split}")
print(f"   📊 Lý do:")
print(f"      - MAPE: {splits_info[best_overall_split]['mape']:.2f}%")
print(f"      - RMSE: {splits_info[best_overall_split]['rmse']:,.2f} USD")
print(f"      - Best Val Loss: {splits_info[best_overall_split]['best_val_loss']:.6f}")
print(f"      - Overfitting Gap: {abs(splits_info[best_overall_split]['final_val_loss'] - splits_info[best_overall_split]['final_train_loss']):.6f}")
print(f"      - Tập test có {splits_info[best_overall_split]['test_size']:,} mẫu (đủ để đánh giá)")
print(f"      - Huấn luyện ổn định với {splits_info[best_overall_split]['epochs']} epochs")

print(f"\n4. NHẬN XÉT CHUNG:")
if best_overall_split == '7:3':
    print("   • Split 7:3 cân bằng tốt giữa kích thước tập train và test")
    print("   • Tập test đủ lớn để đánh giá độ tin cậy của mô hình")
    print("   • Hiệu suất dự đoán tốt với mức overfitting chấp nhận được")
elif best_overall_split == '8:2':
    print("   • Split 8:2 có nhiều dữ liệu train hơn, giúp mô hình học tốt hơn")
    print("   • Tập test vẫn đủ lớn để đánh giá")
    print("   • Cân bằng tốt giữa hiệu suất và độ tin cậy")
else:  # 9:1
    print("   • Split 9:1 tối đa hóa dữ liệu train")
    print("   • Có thể có rủi ro về độ tin cậy do tập test nhỏ")
    print("   • Phù hợp khi có ít dữ liệu và cần tối ưu hiệu suất")

print(f"\n   ⚠️  LƯU Ý: Với dữ liệu time series như XRP, nên chọn split cân bằng")
print(f"   để đảm bảo tập test đủ lớn và đại diện cho nhiều giai đoạn thị trường khác nhau.")
================================================================================
PHÂN TÍCH VÀ KHUYẾN NGHỊ
================================================================================

1. PHÂN TÍCH THEO TỪNG TIÊU CHÍ:
   • Tốt nhất theo MAPE: 8:2 (0.04%)
   • Tốt nhất theo RMSE: 8:2 (0.10 USD)
   • Tốt nhất theo Val Loss: 8:2 (0.002149)
   • Ít overfitting nhất: 8:2 (gap: 0.000453)

2. ĐIỂM TỔNG HỢP (trọng số: MAPE=30%, RMSE=30%, Val_Loss=25%, Overfitting=15%):
   • 7:3: 2.00 điểm
   • 7:3: 2.00 điểm
   • 8:2: 1.00 điểm
   • 8:2: 1.00 điểm
   • 9:1: 3.00 điểm
   • 9:1: 3.00 điểm

3. KẾT LUẬN VÀ KHUYẾN NGHỊ:
   🏆 MÔ HÌNH TỐT NHẤT: Split 8:2
   📊 Lý do:
      - MAPE: 0.04%
      - RMSE: 0.10 USD
      - Best Val Loss: 0.002149
      - Overfitting Gap: 0.000453
      - Tập test có 674 mẫu (đủ để đánh giá)
      - Huấn luyện ổn định với 60 epochs

4. NHẬN XÉT CHUNG:
   • Split 8:2 có nhiều dữ liệu train hơn, giúp mô hình học tốt hơn
   • Tập test vẫn đủ lớn để đánh giá
   • Cân bằng tốt giữa hiệu suất và độ tin cậy

   ⚠️  LƯU Ý: Với dữ liệu time series như XRP, nên chọn split cân bằng
   để đảm bảo tập test đủ lớn và đại diện cho nhiều giai đoạn thị trường khác nhau.
In [106]:
# Vẽ biểu đồ radar cho so sánh tổng thể
import numpy as np
import matplotlib.pyplot as plt

def create_radar_chart():
    # Chuẩn hóa các metrics về scale 0-1 (1 là tốt nhất)
    metrics = ['MAPE', 'RMSE', 'Val_Loss', 'Overfitting', 'Test_Size']
    
    # Lấy giá trị của từng metric (đảo ngược để 1 là tốt nhất)
    data = {}
    for split in splits:
        mape_norm = 1 - (splits_info[split]['mape'] - min([splits_info[s]['mape'] for s in splits])) / \
                   (max([splits_info[s]['mape'] for s in splits]) - min([splits_info[s]['mape'] for s in splits]))
        
        rmse_norm = 1 - (splits_info[split]['rmse'] - min([splits_info[s]['rmse'] for s in splits])) / \
                   (max([splits_info[s]['rmse'] for s in splits]) - min([splits_info[s]['rmse'] for s in splits]))
        
        val_loss_norm = 1 - (splits_info[split]['best_val_loss'] - min([splits_info[s]['best_val_loss'] for s in splits])) / \
                       (max([splits_info[s]['best_val_loss'] for s in splits]) - min([splits_info[s]['best_val_loss'] for s in splits]))
        
        overfitting_gaps = [abs(splits_info[s]['final_val_loss'] - splits_info[s]['final_train_loss']) for s in splits]
        overfitting_norm = 1 - (abs(splits_info[split]['final_val_loss'] - splits_info[split]['final_train_loss']) - min(overfitting_gaps)) / \
                          (max(overfitting_gaps) - min(overfitting_gaps))
        
        test_size_norm = (splits_info[split]['test_size'] - min([splits_info[s]['test_size'] for s in splits])) / \
                        (max([splits_info[s]['test_size'] for s in splits]) - min([splits_info[s]['test_size'] for s in splits]))
        
        data[split] = [mape_norm, rmse_norm, val_loss_norm, overfitting_norm, test_size_norm]
    
    # Tạo radar chart
    angles = np.linspace(0, 2 * np.pi, len(metrics), endpoint=False).tolist()
    angles += angles[:1]  # Complete the circle
    
    fig, ax = plt.subplots(figsize=(10, 10), subplot_kw=dict(projection='polar'))
    
    colors = ['#1f77b4', '#ff7f0e', '#2ca02c']
    
    for i, (split, values) in enumerate(data.items()):
        values += values[:1]  # Complete the circle
        ax.plot(angles, values, 'o-', linewidth=2, label=f'Split {split}', color=colors[i])
        ax.fill(angles, values, alpha=0.25, color=colors[i])
    
    ax.set_xticks(angles[:-1])
    ax.set_xticklabels(metrics)
    ax.set_ylim(0, 1)
    ax.set_title('So sánh tổng thể các Split (1 = Tốt nhất)', size=16, fontweight='bold', pad=20)
    ax.legend(loc='upper right', bbox_to_anchor=(1.2, 1.0))
    ax.grid(True)
    
    plt.tight_layout()
    plt.show()

create_radar_chart()
No description has been provided for this image

Kết luận cuối cùng¶

Dựa trên phân tích toàn diện các tiêu chí đánh giá, mô hình RNN với split tỉ lệ dữ liệu tốt nhất đã được xác định.

Các yếu tố được xem xét:

  • MAPE (Mean Absolute Percentage Error): Đo lường độ chính xác dự đoán
  • RMSE (Root Mean Square Error): Đo lường sai số tuyệt đối
  • Validation Loss: Đo lường hiệu suất trên tập validation
  • Overfitting Gap: Đo lường mức độ overfitting
  • Kích thước tập test: Đảm bảo độ tin cậy trong đánh giá

Khuyến nghị sử dụng: Mô hình với tỉ lệ chia dữ liệu được đánh giá cao nhất sẽ được sử dụng cho các dự đoán cuối cùng về giá XRP.